Reserving Relocatable Address Space in ELF Section - gcc

Here is an example project that adds a binary section to an elf object file. This works with GCC:
#include <unistd.h>
extern char _binary_metamorphosis_txt_start;
int main() {
char* x = &_binary_metamorphosis_txt_start;
int r;
r = write(1,"Starting:\n",10);
r = write(1,x,40);
r = write(1,"\n",1);
return r;
}
In the unlinked assembly, we can see:
leaq _binary_metamorphosis_txt_start(%rip), %rsi
Then, we stick a bunch of text into an object file:
> vim metamorphosis.txt
... copy Franz Kafka's classic work into a text file ...
> ld -r -b binary metamorphosis.txt -o metamorphosis.o
> objdump -x metamorphosis.o
metamorphosis.o: file format elf64-x86-64
...
SYMBOL TABLE:
0000000000000000 l d .data 0000000000000000 .data
0000000000009587 g .data 0000000000000000 _binary_metamorphosis_txt_end
0000000000000000 g .data 0000000000000000 _binary_metamorphosis_txt_start
0000000000009587 g *ABS* 0000000000000000 _binary_metamorphosis_txt_size
Then we compile and link it all together:
> gcc -O -Wall main.c metamorphosis.o
> ./a.out
Starting:
One morning, when Gregor Samsa woke from
Cool. It works. But what if, instead of linking some extra content into my binary, I want to reserve some space and then mmap something in myself. I don't want to hardcode the virtual address. I just want to reserve space near the text, data, and bss sections. And I don't want it to start out backed by anything. From the previous example, the binary I end up with has these sections (the contents of The Metamorphosis end up in .data after linking):
> readelf -W -S a.out
There are 29 section headers, starting at offset 0xaf50:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000238 000238 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 000254 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 0000000000000274 000274 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000298 000298 00001c 00 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002b8 0002b8 0000a8 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000360 000360 000083 00 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000003e4 0003e4 00000e 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000003f8 0003f8 000020 00 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000418 000418 0000c0 18 A 5 0 8
[10] .rela.plt RELA 00000000000004d8 0004d8 000018 18 AI 5 22 8
[11] .init PROGBITS 00000000000004f0 0004f0 000017 00 AX 0 0 4
[12] .plt PROGBITS 0000000000000510 000510 000020 10 AX 0 0 16
[13] .plt.got PROGBITS 0000000000000530 000530 000008 08 AX 0 0 8
[14] .text PROGBITS 0000000000000540 000540 0001d2 00 AX 0 0 16
[15] .fini PROGBITS 0000000000000714 000714 000009 00 AX 0 0 4
[16] .rodata PROGBITS 0000000000000720 000720 00000f 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000000000000730 000730 00003c 00 A 0 0 4
[18] .eh_frame PROGBITS 0000000000000770 000770 000100 00 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000200db8 000db8 000008 08 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000200dc0 000dc0 000008 08 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000200dc8 000dc8 0001f0 10 WA 6 0 8
[22] .got PROGBITS 0000000000200fb8 000fb8 000048 08 WA 0 0 8
[23] .data PROGBITS 0000000000201000 001000 009597 00 WA 0 0 8
[24] .bss NOBITS 000000000020a597 00a597 000001 00 WA 0 0 1
[25] .comment PROGBITS 0000000000000000 00a597 000029 01 MS 0 0 1
[26] .symtab SYMTAB 0000000000000000 00a5c0 000630 18 27 43 8
[27] .strtab STRTAB 0000000000000000 00abf0 000260 00 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00ae50 0000fe 00 0 0 1
The very first section has type NULL. It also has no flags (not even allocate). There are also .symtab, .strtab, and .shstrtab section that are not allocated, but unlike the NULL section, these have non-zero size. However, I don't think any of these cause virtual address space to be reserved. What I'm trying to do is get GCC to emit something like a NULL section but with a non-zero size of my choosing. And it should provide a promise than nothing else will use that virual address space. That way, at runtime, I could back it with memory from a file using MAP_FIXED:
extern char _my_unmapped_section_start;
int main() {
char* x = &_my_unmapped_section_start;
int fd = open("/etc/bunch_of_data.bin", O_RDONLY);
mmap(x, 4096 * 100, PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0);
char theThousandthCharacter = x[999];
...
}
I don't even want it to start out zeroed like a bss section is. If anything, I want it to be like a PROT_NONE mapping. I've tried going about this several ways:
Linker scripts with NOLOAD or DSECT
Patching object files with objcopy --add-section
I've not been able to get either of these working though. What I'm trying to figure out is whether or not it's possible to get a ELF binary that has think behavior. I'm not totally sure whether or not ELF can even represent this. If it can, then is there a straightforward way to get gcc and ld to do this?
To address the "XY problem" concern, I'll add that I'm not actually trying to use C for anything. This is just part of an idea for implementing a high-level language. I'm pondering how to share read-only (immutable) data that's part of the standard library that I don't want to duplicate into every compiled binary. It would be nice to get RIP-relative addressing for this data rather than going through a GOT like C would typically do in this situation. For function calls, ASLR on shared objects (the standard library) is pretty standard and provides some amount of attack mitigation, so I'm fine with the standard "go through of GOT" on function calls. But on immutable data (in a read-only memory map), I don't belive there is any reason to prefer the extra indirection. So I was thinking about how to make it possible to skip the GOT for data.

Related

What binary elf output format allows me to add a .sram section in my binary when linking?

I am trying to compile an application using arm-none-eabi-gcc and would like specific section names to be contained in my binary. The binary is usually compiled by my IDE.
From the official GNU documentation:
SECTIONS { ...
secname : {
contents
}
... }
secname must meet the constraints of your output format. In formats which only support a limited number of sections, such as a.out, the name must be one of the names supported by the format (a.out, for example, allows only .text, .data or .bss).
The last sentence implies that depending on the output format you can have other or more sectionnames. The binary generated by my IDE contains amongst other things the following sections:
$ readelf -S TARGETBINARY.elf
There are 27 section headers, starting at offset 0x5335d8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .sram PROGBITS 34000000 010000 053384 00 WAX 0 0 16
[ 2] .non_cacheable PROGBITS 34180000 070000 040000 00 WAX 0 0 256
[ 3] .ARM.exidx ARM_EXIDX 341c0000 0b0000 000008 00 AL 1 0 4
[ 4] .heap NOBITS 341c0008 0b0008 001000 00 WA 0 0 1
[ 5] .llce_boot_end PROGBITS 43840000 0b0008 000000 00 W 0 0 1
[ 6] .can_43_llce_shar PROGBITS 43800000 0b0008 000000 00 W 0 0 1
[ 7] .lin_43_llce_shar PROGBITS 4383d000 0b0008 000000 00 W 0 0 1
[ 8] .llce_meas_shared PROGBITS 4384ffe0 0b0008 000000 00 W 0 0 1
[ 9] .shareable_ram_bs PROGBITS 22c00000 0b0008 000000 00 W 0 0 1
[10] .shareable_ram_da PROGBITS 22c00000 0b0008 000000 00 W 0 0 1
[11] .debug_info PROGBITS 00000000 0b0008 12ade1 00 0 0 1
[12] .debug_abbrev PROGBITS 00000000 1dade9 01dceb 00 0 0 1
[13] .debug_aranges PROGBITS 00000000 1f8ad8 002848 00 0 0 8
[14] .debug_macro PROGBITS 00000000 1fb320 06d270 00 0 0 1
[15] .debug_line PROGBITS 00000000 268590 091d40 00 0 0 1
[16] .debug_str PROGBITS 00000000 2fa2d0 1a4bbc 01 MS 0 0 1
[17] .comment PROGBITS 00000000 49ee8c 000080 01 MS 0 0 1
[18] .ARM.attributes ARM_ATTRIBUTES 00000000 49ef0c 000037 00 0 0 1
[19] .debug_loc PROGBITS 00000000 49ef43 059247 00 0 0 1
[20] .debug_ranges PROGBITS 00000000 4f8190 0045a0 00 0 0 8
[21] .debug_frame PROGBITS 00000000 4fc730 00e310 00 0 0 4
[22] .stab PROGBITS 00000000 50aa40 00009c 0c 23 0 4
[23] .stabstr STRTAB 00000000 50aadc 00014d 00 0 0 1
[24] .symtab SYMTAB 00000000 50ac2c 017910 10 25 3938 4
[25] .strtab STRTAB 00000000 52253c 010f3e 00 0 0 1
[26] .shstrtab STRTAB 00000000 53347a 00015e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
y (purecode), p (processor specific)
$
The generated binary info:
$file TARGETBINARY.elf
TARGETBINARY.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
Nothing fancy regarding file's output I guess...
As you can see this elf format does not for instance have a section named .bss, .text, etc... Instead it has amongst other things a .sram section, which is precisely what I want to have as well in my binary. Yet when I try to have a .sram section and no .bss section the linker tells me I am missing a .bss section. Which makes me believe I must be missing a linker option somehow...
This is my simplified linker script:
MEMORY
{
foo : ORIGIN = 0x22C00000, LENGTH = 0x00004000
}
SECTIONS
{
.sram :
{
__sram_bss_start = .;
*(.bss)
*(.bss*)
__sram_bss_end = .;
*(.text)
} > foo
}
This is the linker script used by my IDE: https://pastebin.com/c1JfXbY7
What option would allow me to have such .sram sections etc and no .bss nor .text section?
The GNU linker's documentation speaks about "--oformat=output-format" but there is not much info here about what possibilities there are for this option that could allow me to achieve what I meed here.
So I read all the relevant parts of the official documentation, but did not find anything usefull so far
As you can see this elf format does not for instance have a section named .bss, .text, etc... Instead it has amongst other things a .sram section, which is precisely what I want to have as well in my binary. Yet when I try to have a .sram section and no .bss section the linker tells me I am missing a .bss section. Which makes me believe I must be missing a linker option somehow...
Why do you want an .sram section versus a .bss? The simple solution is to use .bss. It is used by _start which is code that runs before main. The main routine needs all global and static variable to be set. There are two classes. 'Zero' variables as per .bss and initialized values. By default the _start implementation will look for a .bss section to initialize.
You don't say what libc your are using nor the compiler/linker options. If you insist on having just .sram, then you need to explore options like, -freestanding, -nostartfiles, and -nostdlib. By not having a .bss section you have broken some coupling between the standard library and the compiler/linker.
I think your real question is how to make a 'ram image' that you can program to ram without needing flash to be active as this is not supported by your IDE. There are various ways to this goal. You need to start by exploring the options above; especially -nostartfiles, which will require you to replace them. The other way is to provide the symbols that the linker wants. There is no reason not to name a section '.bss' and put it in RAM. The regions you have 'foo' is better name 'RAM' and you can put multiple section by using the '>RAM' linker syntax.
The options are enumerated in this answer.
Change the build options.
Provide the library.
Provide an alternative library.
Avoid the function call/data use
It would be difficult to avoid global and static data (but possible). You can define the symbols that the linker/startup wants. You haven't actually given an error message. It might be hidden by your 'IDE' which you might want to give details of. Eclipse based tools like 'Modus', STMCubeIDE, etc hide details like this from the developer.

Duplicate section VMAs of 0 in default linker script

arm-none-eabi-gcc's default linker script defines several sections with VMA of 0. Most of these contain debug information:
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1. */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions. */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2. */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2. */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
//snip several more of these, until...
.ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) }
.note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) }
I don't understand what this does, and the explanatory comment on the DWARF sections doesn't help - the sections can't really all have the same start address unless they're all size 0! Plus, the script is assigning address 0 to the sections, not the symbols. And that comment didn't apply to the "Stabs" section...
Using readelf -S shows that they all theoretically have an address of 0, but also all have different offsets - presumably these offsets are their actual addresses if loaded:
[13] .stab PROGBITS 00000000 009a6c 00009c 0c 14 0 4
[14] .stabstr STRTAB 00000000 009b08 00014d 00 0 0 1
...
[16] .debug_aranges PROGBITS 00000000 009cb0 0005f0 00 0 0 8
[17] .debug_info PROGBITS 00000000 00a2a0 0110c9 00 0 0 1
[18] .debug_abbrev PROGBITS 00000000 01b369 00401d 00 0 0 1
[19] .debug_line PROGBITS 00000000 01f386 0063ed 00 0 0 1
[20] .debug_frame PROGBITS 00000000 025774 00097c 00 0 0 4
[21] .debug_str PROGBITS 00000000 0260f0 001f29 01 MS 0 0 1
[22] .debug_line_str PROGBITS 00000000 028019 0000b3 01 MS 0 0 1
[23] .debug_loclists PROGBITS 00000000 0280cc 00221c 00 0 0 1
[24] .debug_rnglists PROGBITS 00000000 02a2e8 000495 00 0 0 1
[25] .ARM.attributes ARM_ATTRIBUTES 00000000 02a77d 00002e 00 0 0 1
The only idea I have is that virtual address 0 might be considered a special address, which is not taken literally, but instead means that sections with this address will not be loaded into memory (and so will not have a load address) unless the code is being run under a debugger. I have not been able to find any evidence to support this, however.
Can anyone explain to me what is actually happening with these duplicate addresses?
This is explained in the elf(5) man page:
sh_flags
...
SHF_ALLOC
This section occupies memory during process
execution. Some control sections do not reside in
the memory image of an object file. This attribute
is off for those sections.
sh_addr
If this section appears in the memory image of a process,
this member holds the address at which the section's first
byte should reside. Otherwise, the member contains zero.
You should see that the SHF_ALLOC flag is unset for those sections (no A in the Flags field of the readelf -S output) and so they do not reside in the memory image. There is thus no need to specify a start address, so that field is set to zero.
These sections actually do not need to be loaded into memory by exec* under any circumstances, debugger or not. They are certainly not needed for the programmer to run, and when debugging, the debugger will open and read the binary file to parse out that data, as a separate operation from executing the program.

How to map the section to the segment from an ELF output file?

Well I have written a bootloader in assembly and trying to load a C kernel from it.
This is the bootloader:
bits 16
xor ax,ax
jmp 0x0000:boot
extern kernel_main
global boot
boot:
mov ah, 0x02 ; load second stage to memory
mov al, 1 ; numbers of sectors to read into memory
mov dl, 0x80 ; sector read from fixed/usb disk ;0 for floppy; 0x80 for hd
mov ch, 0 ; cylinder number
mov dh, 0 ; head number
mov cl, 2 ; sector number
mov bx, 0x8000 ; load into es:bx segment :offset of buffer
int 0x13 ; disk I/O interrupt
mov ax, 0x2401
int 0x15 ; enable A20 bit
mov ax, 0x3
int 0x10 ; set vga text mode 3
cli
lgdt [gdt_pointer] ; load the gdt table
mov eax, cr0
or eax,0x1 ; set the protected mode bit on special CPU reg cr0
mov cr0, eax
jmp CODE_SEG:boot2 ; long jump to the code segment
gdt_start:
dq 0x0
gdt_code:
dw 0xFFFF
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xFFFF
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
gdt_pointer:
dw gdt_end - gdt_start
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
bits 32
boot2:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; mov esi,hello
; mov ebx,0xb8000
;.loop:
; lodsb
; or al,al
; jz haltz
; or eax,0x0100
; mov word [ebx], ax
; add ebx,2
; jmp .loop
;haltz:
;hello: db "Hello world!",0
mov esp,kernel_stack_top
jmp kernel_main
cli
hlt
times 510 -($-$$) db 0
dw 0xaa55
section .bss
align 4
kernel_stack_bottom: equ $
resb 16384 ; 16 KB
kernel_stack_top:
and this is the C kernel:
__asm__("cli\n");
void kernel_main(void){
const char string[] = "012345678901234567890123456789012345678901234567890123456789012";
volatile unsigned char* vid_mem = (unsigned char*) 0xb8000;
int j=0;
while(string[j]!='\0'){
*vid_mem++ = (unsigned char) string[j++];
*vid_mem++ = 0x09;
}
for(;;);
}
Now I am compiling both the source separately into an ELF output file.
And linking them through a linker script and output a raw binary file and load it with qemu.
Linker script:
ENTRY(boot)
OUTPUT_FORMAT("binary")
SECTIONS{
. = 0x7c00;
.boot1 : {
*(.boot)
}
.kernel : AT(0x7e00){
*(.text)
*(.rodata)
*(.data)
_bss_start = .;
*(.bss)
*(COMMON)
_bss_end = .;
*(.comment)
*(.symtab)
*(.shstrtab)
*(.strtab)
}
/DISCARD/ : {
*(.eh_frame)
}
}
with a build script:
nasm -f elf32 boot.asm -o boot.o
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 kernel.c -o kernel.o -e kernel_main -Ttext 0x0 -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -nostdlib -Wall -Wextra
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-ld boot.o kernel.o -o kernel.bin -T linker3.ld
qemu-system-x86_64 kernel.bin
But I am running into a little problem.
notice that string in the C kernel
const char string[] = "012345678901234567890123456789012345678901234567890123456789012";
when its size is equal to or less than 64 bytes (along with the null termination). then the program works correctly.
however when the string size increases from 64 bytes then the program seems to not work
I was trying to debug it myself and observed that when the string size is less than or equal to 64 bytes then the output ELF file, the kernel.o have following contents :
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x1
Start of program headers: 52 (bytes into file)
Start of section headers: 4412 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 7
Section header string table index: 4
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 001000 0000bd 00 AX 0 0 1
[ 2] .eh_frame PROGBITS 000000c0 0010c0 000034 00 A 0 0 4
[ 3] .comment PROGBITS 00000000 0010f4 000011 01 MS 0 0 1
[ 4] .shstrtab STRTAB 00000000 001105 000034 00 0 0 1
[ 5] .symtab SYMTAB 00000000 001254 0000a0 10 6 6 4
[ 6] .strtab STRTAB 00000000 0012f4 00002e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x00000000 0x00000000 0x000f4 0x000f4 R E 0x1000
Section to Segment mapping:
Segment Sections...
00 .text .eh_frame
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 000000c0 0 SECTION LOCAL DEFAULT 2
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 FILE LOCAL DEFAULT ABS kernel.c
5: 00000000 0 FILE LOCAL DEFAULT ABS
6: 00000001 188 FUNC GLOBAL DEFAULT 1 kernel_main
7: 000010f4 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
8: 000010f4 0 NOTYPE GLOBAL DEFAULT 2 _edata
9: 000010f4 0 NOTYPE GLOBAL DEFAULT 2 _end
No version information found in this file.
However when the size of the string is more than 64 bytes the contents are like this:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x1
Start of program headers: 52 (bytes into file)
Start of section headers: 4432 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 8
Section header string table index: 5
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 001000 000083 00 AX 0 0 1
[ 2] .rodata PROGBITS 00000084 001084 000041 00 A 0 0 4
[ 3] .eh_frame PROGBITS 000000c8 0010c8 000038 00 A 0 0 4
[ 4] .comment PROGBITS 00000000 001100 000011 01 MS 0 0 1
[ 5] .shstrtab STRTAB 00000000 001111 00003c 00 0 0 1
[ 6] .symtab SYMTAB 00000000 001290 0000b0 10 7 7 4
[ 7] .strtab STRTAB 00000000 001340 00002e 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x00000000 0x00000000 0x00100 0x00100 R E 0x1000
Section to Segment mapping:
Segment Sections...
00 .text .rodata .eh_frame
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000084 0 SECTION LOCAL DEFAULT 2
3: 000000c8 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 FILE LOCAL DEFAULT ABS kernel.c
6: 00000000 0 FILE LOCAL DEFAULT ABS
7: 00000001 130 FUNC GLOBAL DEFAULT 1 kernel_main
8: 00001100 0 NOTYPE GLOBAL DEFAULT 3 __bss_start
9: 00001100 0 NOTYPE GLOBAL DEFAULT 3 _edata
10: 00001100 0 NOTYPE GLOBAL DEFAULT 3 _end
No version information found in this file.
I noticed that the string is now in the .rodata section with a size of 41 hex or 65 bytes, which has to be mapped to a segment, possibly the 0th segment which is NULL.
And that the program is unable to find the .rodata.
I am unable to make it work. I understand the ELF structure but I don't know how to work with them.
Two serious issues cause most of the problems are:
You load the second sector of the disk to 0x0000:0x8000 when all of the code expect the kernel to be loaded after the bootloader at 0x0000:0x7e00
You compile your kernel.c straight to an executable name kernel.o. You should compile it to a proper object file so it can go through the expected linking phase when you run ld.
To fix the problem with the kernel being loaded into memory at the wrong memory location, change:
mov bx, 0x8000 ; load into es:bx segment :offset of buffer
to:
mov bx, 0x7e00 ; load into es:bx segment :offset of buffer
To fix the issue of compiling kernel.cto an executable ELF file named kernel.o remove the -e kernel_main -Ttext 0x0 and replace it with -c. -c option forces GCC to produce an object file that can be properly linked with LD. Change:
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 kernel.c -o kernel.o -e kernel_main -Ttext 0x0 -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -nostdlib -Wall -Wextra
to:
/home/rakesh/Desktop/cross-compiler/i686-elf-4.9.1-Linux-x86_64/bin/i686-elf-gcc -m32 -c kernel.c -o kernel.o -nostdlib -ffreestanding -std=gnu99 -mno-red-zone -fno-exceptions -Wall -Wextra
Reason for Failure with Longer Strings
The reason the string with less than 64 bytes worked is because the compiler generated code in a position independent way by initializing the array on the stack with immediate values. When the size reached 64 bytes the compiler placed the string into the .rodata section and then initialized the array on the stack by copying it from the .rodata. This made your code position dependent. Your code was loaded at the wrong offsets and had incorrect origin points yielding code referencing incorrect addresses, so it failed.
Other Observations
You should initialize your BSS (.bss) section to 0 before calling kernel_main. This can be done in assembly by iterating through all the bytes from offset _bss_start to offset _bss_end.
The .comment section will be emitted into your binary file wasting bytes as a result. You should put it in the /DISCARD/ section.
You should place the BSS section in your linker script after all the others so it doesn't take up space in kernel.bin
In boot.asm you should set SS:SP (stack pointer) near the beginning before reading disk sectors. It should be set to a place that won't interfere with your code. This is especially important when reading data into memory from disk since you don't know where the BIOS placed the current stack. You don't want to read on top of the current stack area. Setting it just below the bootloader at 0x0000:0x7c00 should work.
Before calling into C code you should clear the direction flag to ensure string instructions use forward movement. You can do this by using the CLD instruction.
In boot.asmyou can make your code more generic by using the boot drive number passed by the BIOS in the DL register rather than hard coding it to the value 0x80 (0x80 being the first hard drive)
You might consider turning on optimization with -O3, or using optimization level -Os to optimize for code size.
Your linker script doesn't quite work the way you expect although it produces the correct results. You never declared .boot section in your NASM file so nothing actually gets placed in the .boot1 output section in the linker script. It works because it gets included in the .text section in the .kernel output section.
It is preferable to remove the padding and boot signature from the assembly file and move it to the linker script
Instead of having your linker script output a binary file directly, it is more useful to output to the default ELF executable format. You can then use OBJCOPY to convert the ELF file to a binary file. This allows you to build with debug information which will appear as part of the ELF executable. The ELF executable can be used to symbolically debug your binary kernel in QEMU.
Rather than use LD directly for linking, use GCC. This has the advantage that the libgcc library can be added without specifying the full path to the library. libgcc is a set of routines that may be needed for C code generation with GCC
Revised source code, linker script and build commands with the observations above taken into account:
boot.asm:
bits 16
section .boot
extern kernel_main
extern _bss_start
extern _bss_len
global boot
jmp 0x0000:boot
boot:
; Place realmode stack pointer below bootloader where it doesn't
; get in our way
xor ax, ax
mov ss, ax
mov sp, 0x7c00
mov ah, 0x02 ; load second stage to memory
mov al, 1 ; numbers of sectors to read into memory
; Remove this, DL is already set by BIOS to current boot drive number
; mov dl, 0x80 ; sector read from fixed/usb disk ;0 for floppy; 0x80 for hd
mov ch, 0 ; cylinder number
mov dh, 0 ; head number
mov cl, 2 ; sector number
mov bx, 0x7e00 ; load into es:bx segment :offset of buffer
int 0x13 ; disk I/O interrupt
mov ax, 0x2401
int 0x15 ; enable A20 bit
mov ax, 0x3
int 0x10 ; set vga text mode 3
cli
lgdt [gdt_pointer] ; load the gdt table
mov eax, cr0
or eax,0x1 ; set the protected mode bit on special CPU reg cr0
mov cr0, eax
jmp CODE_SEG:boot2 ; long jump to the code segment
gdt_start:
dq 0x0
gdt_code:
dw 0xFFFF
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xFFFF
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
gdt_pointer:
dw gdt_end - gdt_start
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
bits 32
boot2:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Zero out the BSS area
cld
mov edi, _bss_start
mov ecx, _bss_len
xor eax, eax
rep stosb
mov esp,kernel_stack_top
call kernel_main
cli
hlt
section .bss
align 4
kernel_stack_bottom: equ $
resb 16384 ; 16 KB
kernel_stack_top:
kernel.c:
void kernel_main(void){
const char string[] = "01234567890123456789012345678901234567890123456789012345678901234";
volatile unsigned char* vid_mem = (unsigned char*) 0xb8000;
int j=0;
while(string[j]!='\0'){
*vid_mem++ = (unsigned char) string[j++];
*vid_mem++ = 0x09;
}
for(;;);
}
linker3.ld:
ENTRY(boot)
SECTIONS{
. = 0x7c00;
.boot1 : {
*(.boot);
}
.sig : AT(0x7dfe){
SHORT(0xaa55);
}
. = 0x7e00;
.kernel : AT(0x7e00){
*(.text);
*(.rodata*);
*(.data);
_bss_start = .;
*(.bss);
*(COMMON);
_bss_end = .;
_bss_len = _bss_end - _bss_start;
}
/DISCARD/ : {
*(.eh_frame);
*(.comment);
}
}
Commands to build this bootloader and kernel:
nasm -g -F dwarf -f elf32 boot.asm -o boot.o
i686-elf-gcc -g -O3 -m32 kernel.c -c -o kernel.o -ffreestanding -std=gnu99 \
-mno-red-zone -fno-exceptions -Wall -Wextra
i686-elf-gcc -nostdlib -Wl,--build-id=none -T linker3.ld boot.o kernel.o \
-lgcc -o kernel.elf
objcopy -O binary kernel.elf kernel.bin
To symbolically debug the 32-bit kernel with QEMU you can launch QEMU this way:
qemu-system-i386 -fda kernel.bin -S -s &
gdb kernel.elf \
-ex 'target remote localhost:1234' \
-ex 'break *kernel_main' \
-ex 'layout src' \
-ex 'continue'
This will start up your kernel.bin file in QEMU and then remotely connect the GDB debugger. The layout should show the source code and break on kernel_main.

What does loadable mean in microcontrollers in the sense of linikers

I'm trying to get familiar with the linking and startup procedures in ARM Cortex-M4 microcontrollers. Looking through the linker scripts almost all the sections are marked as loadable.
At first I thought that meant it would be copied from flash to RAM, but then I learned that doing that is handled in a different way. So what does it mean for a section in flash to be loadable? Isn't it already loaded and run from the location in flash? Also, I'm referring to a section section containing instructions.
Does loadable in this context mean loading by the debugger into the device?
a fully functional cortex-m program
flash.s
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.align
.thumb_func
.globl dummy
dummy:
bx lr
so.c
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int ra;
for(ra=0;ra<10;ra++) dummy(ra);
return(0);
}
flash.ld
MEMORY
{
rom : ORIGIN = 0x00000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
build
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -O2 -nostdlib -nostartfiles -ffreestanding -mthumb -c so.c -o so.o
arm-none-eabi-ld -o so.elf -T flash.ld flash.o so.o
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf so.bin -O binary
(can use cortex-m4 either works)
so.list
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000011 andeq r0, r0, r1, lsl r0
8: 00000017 andeq r0, r0, r7, lsl r0
c: 00000017 andeq r0, r0, r7, lsl r0
00000010 <reset>:
10: f000 f804 bl 1c <notmain>
14: e7ff b.n 16 <hang>
00000016 <hang>:
16: e7fe b.n 16 <hang>
00000018 <dummy>:
18: 4770 bx lr
1a: 46c0 nop ; (mov r8, r8)
0000001c <notmain>:
1c: b510 push {r4, lr}
1e: 2400 movs r4, #0
20: 0020 movs r0, r4
22: 3401 adds r4, #1
24: f7ff fff8 bl 18 <dummy>
28: 2c0a cmp r4, #10
2a: d1f9 bne.n 20 <notmain+0x4>
2c: 2000 movs r0, #0
2e: bc10 pop {r4}
30: bc02 pop {r1}
32: 4708 bx r1
being a less complicated linker script and program the elf has less stuff
part of readelf
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 010000 000034 00 AX 0 0 4
[ 2] .ARM.attributes ARM_ATTRIBUTES 00000000 010034 00002d 00 0 0 1
[ 3] .comment PROGBITS 00000000 010061 000011 01 MS 0 0 1
[ 4] .symtab SYMTAB 00000000 010074 0000f0 10 5 12 4
[ 5] .strtab STRTAB 00000000 010164 00003d 00 0 0 1
[ 6] .shstrtab STRTAB 00000000 0101a1 00003a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
y (purecode), p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x010000 0x00000000 0x00000000 0x00034 0x00034 R E 0x10000
hexdump -C so.bin
00000000 00 10 00 20 11 00 00 00 17 00 00 00 17 00 00 00 |... ............|
00000010 00 f0 04 f8 ff e7 fe e7 70 47 c0 46 10 b5 00 24 |........pG.F...$|
00000020 20 00 01 34 ff f7 f8 ff 0a 2c f9 d1 00 20 10 bc | ..4.....,... ..|
00000030 02 bc 08 47 |...G|
00000034
a "binary" comes in many flavors, it is an unfortunately poorly named term as it is so confusing. Everything you see in the disassembly above is required for the program to run, that is the real binary part of this, it has to be loaded wherever you need it loaded to run. if on an operating system then the operating system reads the elf file extracts the loadable sections with their addresses/offsets and loads them into memory before launching at the entry point. Take advantage of tools already having the elf file format and we can re-use some of that for microcontrollers, we cant/dont normally use the entry point as that makes no sense, we have to make the vector/entry point match what the hardware needs, in this case a vector table.
The hexdump coming from the objcopy also shows the parts we have to have visible to the processor for the program to run, or loaded in address/memory space (flash is in memory or address space in this case).
But the "binary" file, the elf also contains debug symbols in case you want to pull up a debugger, add some more options on the toolchain command line and you get even more information about where these items are in the source code file so you could in some cases see the high level langauge when single stepping through code. Those are not loadable sections, they are descriptive of the program or just there to help out, they are not machine code nor data that the processor needs to execute the program so they do not need to be loaded into the memory space.
yet another "binary" file format
arm-none-eabi-objcopy so.elf -O ihex so.hex
cat so.hex
:100000000010002011000000170000001700000081
:1000100000F004F8FFE7FEE77047C04610B5002483
:1000200020000134FFF7F8FF0A2CF9D1002010BCA2
:0400300002BC0847BF
:00000001FF
it has a little extra information but almost all of it is the part we have to load into the address space for the program to run
another binary file format
S00A0000736F2E7372656338
S1130000001000201100000017000000170000007D
S113001000F004F8FFE7FEE77047C04610B500247F
S113002020000134FFF7F8FF0A2CF9D1002010BC9E
S107003002BC0847BB
S9030000FC
also most of it is program.
elf is just another file format (fairly popular with gnu tools, but still just another file format), it contains the machine code and data required plus a bunch of other stuff, the machine code and data is what we have to load into ram if this is an operating system, or what we ideally load into flash for a microcontroller, but not all microcontrollers are equal some are ram only based and the program is downloaded via usb during enumeration (into ram). and other solutions, or if debugging you could probably have items loaded into ram depending on the mcu and tools, although that is not how the thing boots so that wouldnt really be a good binary.
if you feel the need to zero .bss and to have any .data then you need additional information, the offset and size of .bss and the offset and contents of .data, the bootstrap then zeros and copies those items, and you need that information in the non-volatile flash/rom, it is just more data that is required of the machine code and data needed to run the program. If you are letting others write the code for you then there are possibly tailored linker scripts and bootstrap code that allows you to just have .data items and push a build button on a gui and it all magically is in place when your entry point (main() by convention and or standard) starts execution or the code that represents your high level code at the C entry point.

MIPS32 router: module_init not called for kernel module

I'm developing a kernel module that I want to run on my router. The router model is DGN2200v2 by Netgear. It's running Linux 2.6.30 on MIPS. My problem is that when I load my module it seems that my module_init isn't getting called. I tried to narrow it down by modifying my module_init to return -3 (which indicates an error?) and insmod still reports success. I can see my module in the output of lsmod, but I don't see my printk output using dmesg.
For starters, I wanted to create the simplest possible module:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int my_init(void)
{
printk(KERN_EMERG "init_module() called\n");
return -3;
}
static void my_cleanup(void)
{
printk(KERN_EMERG "cleanup_module() called\n");
}
module_init(my_init);
module_exit(my_cleanup);
This is the Makefile I'm using:
TOOLCHAIN=/home/user/buildroot-2016.08/output/host/usr/bin/mips-buildroot-linux-uclibc-
ARCH=mips
CC = $(TOOLCHAIN)gcc
KBUILD_CFLAGS:=.
EXTRA_CFLAGS := -I/home/user/buildroot-2016.08/output/build/linux-headers-2.6.30/include\
-I/home/user/buildroot-2016.08/output/build/linux-headers-2.6.30/arch/mips/include/asm/mach-mipssim\
-I/home/user/buildroot-2016.08/output/build/linux-headers-2.6.30/arch/mips/include/asm/mach-generic\
-fno-pic -mno-abicalls -O2
obj-m := module.o
KDIR := /home/user/buildroot-2016.08/output/build/linux-headers-2.6.30
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
I'm running make like so:
make ARCH=mips CROSS_COMPILE=/home/user/buildroot-2016.08/output/host/usr/bin/mips-buildroot-linux-uclibc-
which passes successfully.
As you can see, I'm using Buildroot which I (hopefully) configured correctly. I can paste my .config if needed.
I ran objdump on my module and didn't find a problem. In particular, the module_init symbol seems to point to the same place as my my_init function, and it seems to have the code I expect it to:
module.ko: file format elf32-tradbigmips
module.ko
architecture: mips:isa32, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000
private flags = 50001001: [abi=O32] [mips32] [not 32bitmode] [noreorder]
MIPS ABI Flags Version: 0
ISA: MIPS32
GPR size: 32
CPR1 size: 0
CPR2 size: 0
FP ABI: Soft float
ISA Extension: None
ASEs:
None
FLAGS 1: 00000001
FLAGS 2: 00000000
Sections:
Idx Name Size VMA LMA File off Algn
0 .MIPS.abiflags 00000018 00000000 00000000 00000038 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_SAME_SIZE
1 .reginfo 00000018 00000000 00000000 00000050 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_SAME_SIZE
2 .note.gnu.build-id 00000024 00000018 00000018 00000068 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .text 00000040 00000000 00000000 00000090 2**4
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
4 .rodata.str1.4 00000038 00000000 00000000 000000d0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .modinfo 0000005c 00000000 00000000 00000108 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .data 00000000 00000000 00000000 00000170 2**4
CONTENTS, ALLOC, LOAD, DATA
7 .gnu.linkonce.this_module 0000014c 00000000 00000000 00000170 2**2
CONTENTS, ALLOC, LOAD, RELOC, DATA, LINK_ONCE_DISCARD
8 .bss 00000000 00000000 00000000 000002c0 2**4
ALLOC
9 .comment 00000040 00000000 00000000 000002c0 2**0
CONTENTS, READONLY
10 .pdr 00000040 00000000 00000000 00000300 2**2
CONTENTS, RELOC, READONLY
11 .gnu.attributes 00000010 00000000 00000000 00000340 2**0
CONTENTS, READONLY
12 .mdebug.abi32 00000000 00000000 00000000 00000350 2**0
CONTENTS, READONLY
SYMBOL TABLE:
00000000 l d .MIPS.abiflags 00000000 .MIPS.abiflags
00000000 l d .reginfo 00000000 .reginfo
00000018 l d .note.gnu.build-id 00000000 .note.gnu.build-id
00000000 l d .text 00000000 .text
00000000 l d .rodata.str1.4 00000000 .rodata.str1.4
00000000 l d .modinfo 00000000 .modinfo
00000000 l d .data 00000000 .data
00000000 l d .gnu.linkonce.this_module 00000000 .gnu.linkonce.this_module
00000000 l d .bss 00000000 .bss
00000000 l d .comment 00000000 .comment
00000000 l d .pdr 00000000 .pdr
00000000 l d .gnu.attributes 00000000 .gnu.attributes
00000000 l d .mdebug.abi32 00000000 .mdebug.abi32
00000000 l df *ABS* 00000000 module.c
00000000 l F .text 0000002c my_init
0000002c l F .text 00000014 my_cleanup
00000000 l .rodata.str1.4 00000000 $LC0
0000001c l .rodata.str1.4 00000000 $LC1
00000000 l df *ABS* 00000000 module.mod.c
00000000 l O .modinfo 00000023 __mod_srcversion23
00000024 l O .modinfo 00000009 __module_depends
00000030 l O .modinfo 0000002c __mod_vermagic5
00000000 g O .gnu.linkonce.this_module 0000014c __this_module
0000002c g F .text 00000014 cleanup_module
00000000 g F .text 0000002c init_module
00000000 *UND* 00000000 printk
Disassembly of section .MIPS.abiflags:
00000000 <.MIPS.abiflags>:
0: 00002001 movf a0,zero,$fcc0
4: 01000003 0x1000003
...
10: 00000001 movf zero,zero,$fcc0
14: 00000000 nop
Disassembly of section .reginfo:
00000000 <.reginfo>:
0: a2000014 sb zero,20(s0)
...
14: 00007fef 0x7fef
Disassembly of section .note.gnu.build-id:
00000018 <.note.gnu.build-id>:
18: 00000004 sllv zero,zero,zero
1c: 00000014 0x14
20: 00000003 sra zero,zero,0x0
24: 474e5500 c1 0x14e5500
28: c8e5d654 lwc2 $5,-10668(a3)
2c: cb477d3d lwc2 $7,32061(k0)
30: dfa48d71 ldc3 $4,-29327(sp)
34: c2ea16da ll t2,5850(s7)
38: f6bcae7d sdc1 $f28,-20867(s5)
Disassembly of section .text:
00000000 <init_module>:
0: 27bdffe8 addiu sp,sp,-24
4: 3c040000 lui a0,0x0
4: R_MIPS_HI16 $LC0
8: 3c020000 lui v0,0x0
8: R_MIPS_HI16 printk
c: afbf0014 sw ra,20(sp)
10: 24420000 addiu v0,v0,0
10: R_MIPS_LO16 printk
14: 0040f809 jalr v0
18: 24840000 addiu a0,a0,0
18: R_MIPS_LO16 $LC0
1c: 8fbf0014 lw ra,20(sp)
20: 2402fffd li v0,-3
24: 03e00008 jr ra
28: 27bd0018 addiu sp,sp,24
modinfo output also matches what I expect (same modinfo output as for another .ko that's found on the router, except for the srcversion which my module has but the other module on the router doesn't):
filename: /home/user/module/module.ko
srcversion: B0BADBA395A121CF49B74DC
depends:
vermagic: 2.6.30 mod_unload MIPS32_R1 32BIT
It's entirely possible that I messed something up in my Buildroot configuration, or something doesn't quite match the CPU type of the router, but my init code is so minimal that I'm out of ideas as to what could be wrong.
It turns out that the problem was related to a different kernel configuration between my development environment and the router. Specifically, my kernel was using CONFIG_UNUSED_SYMBOLS whereas the router's was not.
The reason this caused a problem even in a trivial module is that when the kernel loads a module it doesn't only look up the module_init symbol in the module's symbol table. Rather, it reads the module struct from the module (from the .gnu.linkonce.this_module section), and then calls the init module through that struct.
The offset of the init function pointer inside the module struct depends on the kernel configuration, which explains why the kernel can't find the init function if the configuration is different.
Thanks to Sam Protsenko for investing a lot of time in helping me crack this!

Resources