Skip to content

arm/linux: dtest and CI segfault#89

Merged
mkobetic merged 10 commits intomainfrom
linux-fix
Apr 15, 2026
Merged

arm/linux: dtest and CI segfault#89
mkobetic merged 10 commits intomainfrom
linux-fix

Conversation

@mkobetic
Copy link
Copy Markdown
Owner

@mkobetic mkobetic commented Apr 8, 2026

So fortunately the CI segfault also reproduced in local Docker, and although that is not exactly a friendly debugging target either (GDB doesn't work in emulated container) I was able to deduce that the issue is that the user dictionary spaces weren't mapped as executable regions. It is possible to get the containerized process to dump the core file (need to invoke ulimit -c unlimited first), but the core file can then be analyzed using GDB outside of the container. The segfault was happening in the synthetic jump instruction we compile into DOES> words and is therefore executed in those spaces.

So, how to mark the memory as executable? We need to mark the memory segments produced by the linker as executable. For that most reasonable way seems to be adding a PHDRS section to the linker file and declaring segments explicitly instead of letting the linker conjure them up, and then assigning each section to a specific segment (the :segment bit at the end of the section definitions).

Coming up with the set of segments was also a journey, because the :segment is only a hint apparently and you need to be careful about making segments span large memory areas and therefore large MemSiz value in the make segments output below. It is very easy to end up with a large segment that contains other segments, usually you'll see the same section mapped to multiple segments in that case. What we want is to have nice tidy segments and each section mapped to exactly one of them.

Below is the final arm/linux layout, note that the segments 02 and 03 containing all the forth sections are marked RWE (E = executable). amramhi is what made the tests segfault, but we also want the userdict section executable for >flash compiled words.

% make segments             
/opt/homebrew/opt/arm-linux-gnueabihf-binutils/bin/arm-linux-gnueabihf-readelf --segments build/amforth.elf

Elf file type is EXEC (Executable file)
Entry point 0x10000
There are 6 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x00010000 0x00010000 0x00018 0x00018 R E 0x1000
  LOAD           0x00a000 0x10000000 0x0004e000 0x00000 0x010e8 RW  0x1000
  LOAD           0x001018 0x00010018 0x00010018 0x087e8 0x3dfe8 RWE 0x1000
  LOAD           0x0000e8 0x100010e8 0x0004e000 0x00000 0xcc714 RWE 0x1000
  LOAD           0x0007fc 0x100ff7fc 0x100ff7fc 0x00000 0x00800 RW  0x1000
  LOAD           0x001000 0x0004e000 0x0004e000 0x00000 0x02000 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text 
   01     .data .bss amramlo 
   02     amforth userdict first_boot userdict2 
   03     amramlo amramhi 
   04     .stack 
   05     pvarena1 pvarena2 

Another tweak in amforth32.ld is the addition of (NOLOAD) flag to some of the sections, which marks the section as not having any content in the file. It was one of the AI recommendations to deal with the slow load problem on RV qemu, but it didn't help because the issue isn't loading things from file but zeroing out of empty memory and the (NOLOAD) doesn't really affect that. But the sections are marked correctly this way and I figured I may as well leave it there.

Otherwise the actual fix is the change of the -device loader to use the .bin file instead of the .elf file and thus bypassing the ELF loading logic altogether:

QEMU := $(QEMU_RV32) -M virt -bios none -device loader,file=build/amforth.bin,addr=0x20010000,cpu-num=0

While fussing around with the segments I had to change the way the amramhi allocation of the available RAM was done, by moving the stack_start computation up and using it in the amramhi definition. This triggered a discovery of an issue on hifive which has so little RAM that there wan't enough room for both the default amount of rampool_size and __stack_size. To solve it I moved __stack_size into config.s and reduced both in hifive's config.s so that things could actually fit.

With these fixes I was able to enable the CI tests for arm/linux and things seem to be happy now.


/* C stack size */
.equ __stack_size, 2048
.global __stack_size
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from amforth32.ld so that it can be overriden in mcu config.s

include $(AMFORTH)/arm/dev/Makefile

LDFLAGS += -z max-page-size=4096
LDFLAGS += -no-pie -static -z max-page-size=4096
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These flags don't fix anything but should be there as they indicate to the linker that the code is not meant to be relocated.

all: config build/amforth.bin build/amforth.hex build/amforth.lst build/amforth.sal build/amforth.sym build/amforth.toc build/amforth.txt build/amforth.html
@echo INFO: Built $(TARGET) $(REVISION) using $(TC_DIR)
@$(SIZE) build/amforth.elf
@cd build && ls -l amforth.bin amforth.hex amforth.elf
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to dump the interesting file sizes at the end of the build

build/%.bin: build/%.elf
@echo "REBUILD $@"
@$(OBJCOPY) -O binary $< $@
test `wc -c <$@` -lt 100000
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks that the bin file didn't grow over 100kb (we're averaging about 50kb atm), could make it tighter if desired, but wanted to leave some room for future growth.

/*
This is the main AmForth flash section housing all the predefined words.
*/
amforth :
Copy link
Copy Markdown
Owner Author

@mkobetic mkobetic Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the diff is confusing, I'm just moving the C data sections (.data, .bss, .dalign, ...) ahead of the amramres and amforth sections. The C data sections can contain bits of alignment padding even when otherwise empty and consequently cause the bin file to span the whole FLASH when allocated at the end of it.

/* C stack size */
__stack_size = 2048;
PROVIDE( _stack_size = __stack_size );

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to config.inc

@mkobetic mkobetic merged commit 1a7f834 into main Apr 15, 2026
3 checks passed
@mkobetic mkobetic deleted the linux-fix branch April 15, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant