Skip to content

arm/linux: dtest and CI segfault#89

Open
mkobetic wants to merge 2 commits intomainfrom
linux-fix
Open

arm/linux: dtest and CI segfault#89
mkobetic wants to merge 2 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

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