Cannot assign address of variable defined in linker script - c

I found a solution, although I don't understand what went wrong. Here is the original question. The solution is at the end.
I am following this Raspberry PI OS tutorial with a few tweaks. As the title says, one assignment appears to fail.
Here is my C code:
extern int32_t __end;
static int32_t *arena;
void init() {
arena = &__end;
assert(0 != arena); // fails
...
The assert triggers! Surely the address shouldn't be 0. __end is declared in my linker script:
ENTRY(_start)
SECTIONS
{
/* Starts at LOADER_ADDR. 0x8000 is a convention. */
. = 0x8000;
__start = .;
.text : {
*(.text)
}
.rodata : { *(.rodata) }
.data : { *(.data) }
/* Define __bss_start and __bss_end for boot.s to set to 0 */
__bss_start=.;
.bss : { *(.bss) }
__bss_end=.;
/* First usable address for the allocator */
. = ALIGN(4);
__end = .;
}
Investigating in GDB (running it in QEMU):
Thread 1 hit Breakpoint 1, init () at os.c:75
75 arena = &__end;
(gdb) p &__end
$1 = (int32_t *) 0x9440
(gdb) p arena
$2 = (int32_t *) 0x0
(gdb) n
76 assert(0 != arena);
(gdb) p arena
$3 = (int32_t *) 0x0
GDB can find __end but my program cannot?
Here are a few other things I tried:
the tutorial's code works without an issue (implying that QEMU and the ARM compiler are working)
the assertion still fails when running without GDB (implying GDB is not the issue)
I am able to assign 0xccc to arena (implying arena is not the issue)
I am not able to assign &__end to a local variable (implying &__end is the issue).
As requested in the comments, this is how I tried to assign to a local variable:
void* arena2 = (void*)&__end;
assert(0 != arena2);
The assertion fails. In GDB:
Thread 1 hit Breakpoint 1, mem_init () at mem.c:77
77 void* arena2 = (void*)&__end;
(gdb) p arena2
$1 = (void *) 0x13
(gdb) p &__end
$2 = (int32_t *) 0x94a4
(gdb) n
78 assert(0 != arena2);
(gdb) p arena2
$3 = (void *) 0x0
(gdb) p &__end
$4 = (int32_t *) 0x94a4
assert(0 != &__end); succeeds (implying &__end is not the issue?)
N.B. This version of assert is not the same as the one in assert.h, but I don't think it causes the problem. It just checks a condition, prints the condition, and goes to a breakpoint. I can reproduce the issue in GDB with the assert commented out.
N.B.2. I previously included the ARM assembly of the C code in case there was a compiler bug
My solution is to edit the linker script to:
ENTRY(_start)
SECTIONS
{
/* Starts at LOADER_ADDR. 0x8000 is a convention. */
. = 0x8000;
__start = .;
.text : {
*(.text)
}
. = ALIGN(4096);
.rodata : { *(.rodata) }
. = ALIGN(4096);
.data : { *(.data) }
. = ALIGN(4096);
/* Define __bss_start and __bss_end for boot.s to set to 0 */
__bss_start = .;
.bss : { *(.bss) }
. = ALIGN(4096);
__bss_end = .;
/* First usable address for the allocator */
. = ALIGN(4096);
__end = .;
}
I don't understand why the additional ALIGNs are important.

The problem you're having here is because the "clear the BSS" loop in boot.S is also clearing some of the compiler-generated data in the ELF file that the C code is using at runtime. Notably, it is accidentally zeroing out the GOT (global offset table) which is in the .got ELF section and which is where the actual address of the __end label has been placed by the linker. So the linker correctly fills in the address in the ELF file, but then the boot.S code zeroes it, and when you try to read it from C then you get zero rather than what you were expecting.
Adding all that alignment in the linker script is probably working around this by coincidentally causing the GOT to not be in the area that gets zeroed.
You can see where the linker has put things by using 'objdump -x myos.elf'. In my test case based on the tutorial you link I see a SYMBOL TABLE which includes among other entries:
000080d4 l .bss 00000004 arena
00000000 l df *ABS* 00000000
000080c8 l O .got.plt 00000000 _GLOBAL_OFFSET_TABLE_
000080d8 g .bss 00000000 __bss_end
0000800c g F .text 00000060 kernel_main
00008000 g .text 00000000 __start
0000806c g .text.boot 00000000 _start
000080d8 g .bss 00000000 __end
00008000 g F .text 0000000c panic
000080c4 g .text.boot 00000000 __bss_start
So you can see that the linker script has set __bss_start to 0x80c4 and __bss_end to 0x80d8, which is a pity because the GOT is at 0x80c4/0x80c8. I think what has happened here is that because you didn't specify explicitly in your linker script where to put the .got and .got.plt sections, the linker has decided to put them after the __bss_start assignment and before the .bss section, so they get covered by the zeroing code.
You can see what the ELF file contents of the .got are with 'objdump --disassemble-all myos.elf', which among other things includes:
Disassembly of section .got:
000080c4 <.got>:
80c4: 000080d8 ldrdeq r8, [r0], -r8 ; <UNPREDICTABLE>
so you can see we have one GOT table entry, whose contents are the address 0x80d8 which is the __end value we want. When the boot.S code zeroes this out your C code reads a 0 rather than the constant it was expecting.
You should probably ensure that the bss start/end are at least 16-aligned, because the boot.S code works via a loop that clears 16 bytes at a time, but I think that if you fix your linker script to explicitly put the .got and .got.plt sections somewhere then you'll find you don't need the 4K alignments everywhere.
FWIW, I diagnosed this using: (1) the QEMU "-d in_asm,cpu,exec,int,unimp,guest_errors -singlestep" options to get a dump of register state and instruction execution and (2) objdump of the ELF file to figure out what the compiler's generated code was actually doing. I had a suspicion this was going to turn out to be either "accidentally zeroed data we shouldn't have" or "failed to include in the image or otherwise initialize data we should have" kind of bug, and so it turned out.
Oh, and the reason GDB was printing the right value for __end when your code wasn't was that GDB could just look directly in the debug/symbol info in the ELF file for the answer; it wasn't doing it by going via the in-memory GOT.

Related

implict intialized variable not in .bss section

[Compiler gcc-arm-8.2-2019.01]
I have a variable in .c:int ep;I expect it is put in .bss section and having 0 initial value, but it is excluded from .bss section after I check the .map file.
If I change the line to:
int ep = 0;
The var is put into .bss section.
My guess is compiler found that the var is a writeonly, so no need initialize its value to 0.
But this var is read by assembly code, also read by another program, which compiler seems not knowing and make the wrong optimize choice.
Any help on this behavior?
excluded from .bss means, in linkscript file I write:
.bss :
{
. = ALIGN(64);
__bss_start = .;
*(.bss)
*(.bss.*)
__bss_end = .;
} > DRAM
but var ep is not between __bss_start and __bss_end, where assembly code use them to do memory clear before jump in C code

How to get the first address of initialized data segment

my program is working on linux using gcc. Through the manual page, I find edata, which represent the first address past the end of the initialized data segment.
But I want know the first address of initialized data segment
How can I get it?
I have tried treating etext as the first address of initialized data segment. Then I got a segment fault when I increase the address and access the variable stored in it. I think some address space between etext and edata was not mapped into virtual memory. Is that right?
That depends on your linker scripts. For example on some platforms you have the symbol __bss_start at the beginning of BSS. It's a symbol without any data associated with it, you can get a pointer to it by extern declaring a variable with that name (only for the sake of taking the address of that variable). For example:
#include <stdio.h>
extern char __bss_start;
int main()
{
printf("%p\n", &__bss_start);
return 0;
}
You find this by looking in the linker script, for example in /usr/lib/ldscripts/elf_x64_64.x:
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
_edata = .; PROVIDE (edata = .);
__bss_start = .; /* <<<<< this is what you're looking for /*
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we don't
pad the .data section. */
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
You can also see the edata you mentioned, but as edata is not reserved for the implementation (the PROVIDE means only to create this symbol if it otherwise isn't used) you should probably use _edata instead.
If you want the address to the start of the data section you could modify the linker script:
__data_start = . ;
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
_edata = .; PROVIDE (edata = .);
__bss_start = .; /* <<<<< this is what you're looking for /*
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we don't
pad the .data section. */
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
You probably want to make a copy of the linker script (look for the right one in /usr/lib/ldscripts, they are different depending on what kind of output you're targeting) and supply it when you compile:
gcc -o execfile source.c -Wl,-T ldscript
Another option if you don't want to modify the linker script could be to use the __executable_start and parse the ELF headers (hoping that the executable is sufficiently linearly mapped)
As for _etext, it is the end of the text section (you can read that in the linker script as well, but I didn't include it in the excerpt), but the text section is followed by rodata, trying to write there is probably going to segfault.
You can use the linux tool size (binutils package in Debian/Ubuntu).
Example
size -A /usr/bin/gcc
results in
/usr/bin/gcc :
section size addr
.interp 28 4194928
.note.ABI-tag 32 4194956
.note.gnu.build-id 36 4194988
.gnu.hash 240 4195024
.dynsym 4008 4195264
.dynstr 2093 4199272
.gnu.version 334 4201366
.gnu.version_r 160 4201704
.rela.dyn 720 4201864
.rela.plt 3240 4202584
.init 14 4205824
.plt 2176 4205840
.text 384124 4208016
.fini 9 4592140
.rodata 303556 4592160
.eh_frame_hdr 8540 4895716
.eh_frame 50388 4904256
.gcc_except_table 264 4954644
.tbss 16 7052632
.init_array 16 7052632
.fini_array 8 7052648
.jcr 8 7052656
.data.rel.ro 3992 7052672
.dynamic 480 7056664
.got 216 7057144
.got.plt 1104 7057384
.data 2520 7058496
.bss 80976 7061024
.gnu_debuglink 12 0
Total 849310

ARM bare metal binary linking

I have an ARM board with ROM at 0x80000000 and RAM at 0x20000000. Board starts execution of raw binary code at 0x80000000.
I managed to get a simple ARM assembler program running on it, but I have to use C instead of ASM. I know I will need to use some kind of linker script, and then manually copy .data section to RAM and clear .bss, setup stack etc. but I haven't found a reliable solution how to do this yet, especially the linker script (it's very messy in my opinion).
Also, I can't get the linker to output a raw binary instead of ELF, but it's not a big deal as I can use objcopy later.
Thanks in advance.
This example is for STM32F051 MCU:
Very simple application as "blinky" (without delays):
// define used registers
#define RCC_AHB1 *(volatile unsigned int *)(0x40021014)
#define GPIOC_MODER *(volatile unsigned int *)(0x48000800)
#define GPIOC_BSRR *(volatile unsigned int *)(0x48000818)
// main program
void mainApp() {
RCC_AHB1 = 1 << 19; // enable clock for GPIOC
GPIOC_MODER = 1 << (9 * 2); // set output on GPIOC.P9
while (1) {
GPIOC_BSRR = 1 << 9; // set output on GPIOC.P9
GPIOC_BSRR = 1 << (9 + 16); // clear output on GPIOC.P9
}
}
// variables for testing memory initialisation
int x = 10;
int y = 0;
int z;
Here is also very simple startup file written in C (also will work in C++), which start application mainApp and initialise static variables .data initialised from ROM and .bss only set to zero, there are variables initialised to zero and uninitialised variables.
extern void mainApp();
// external variables defined in linker script
// address in FLASH where are stored initial data for .data section
extern unsigned int _data_load;
// defines start and end of .data section in RAM
extern unsigned int _data_start;
extern unsigned int _data_end;
// defines start and end of .bss section in RAM
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void resetHandler() {
unsigned int *src, *dst;
// copy .data area
src = &_data_load;
dst = &_data_start;
while (dst < &_data_end) {
*dst++ = *src++;
}
// clear .bss area
dst = &_bss_start;
while (dst < &_bss_end) {
*dst++ = 0;
}
mainApp();
while(1);
}
// _stacktop is defined in linker script
extern unsigned int _stacktop;
// vector table, will be placed on begin of FLASH memory, is defined in linker script
// only reset vector defined, need add other used vectors (especially NMI)
__attribute__((section(".vectors"), used)) void *isr_vectors[] = {
&_stacktop, // first vector is not vector but initial stack position
(void *)resetHandler, // vector which is called after MCU start
};
And finaly linker script which define location and sizes of memories, sections for vector, program (text), data (.data and .bss) a stacktop
MEMORY {
FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 64K
SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 8K
}
SECTIONS {
. = ORIGIN(FLASH);
.text : {
*(.vectors)
*(.text)
} >FLASH
. = ORIGIN(SRAM);
.data ALIGN(4) : {
_data_start = .;
*(.data)
. = ALIGN(4);
_data_end = .;
} >SRAM AT >FLASH
.bss ALIGN(4) (NOLOAD) : {
_bss_start = .;
*(.bss)
. = ALIGN(4);
_bss_end = .;
} >SRAM
_stacktop = ORIGIN(SRAM) + LENGTH(SRAM);
_data_load = LOADADDR(.data);
}
Build this with these command (build and link at once):
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf
In symbol table is possible to see where are stored data:
$ arm-none-eabi-nm -C -l -n -S main.elf
08000000 00000008 T isr_vectors
08000008 00000034 T mainApp
0800003c 0000005c T resetHandler
08000098 A _data_load
20000000 D _data_start
20000000 00000004 D x
20000004 D _data_end
20000004 B _bss_start
20000004 00000004 B y
20000008 B _bss_end
20000008 00000004 B z
20002000 A _stacktop
Also you can look into listing:
arm-none-eabi-objdump -S main.elf
Raw binary:
arm-none-eabi-objcopy -O binary main.elf main.bin
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
__data_rom_start__ = .;
.data : {
__data_start__ = .;
*(.data*)
} > ted AT > bob
__data_end__ = .;
__data_size__ = __data_end__ - __data_start__;
.bss : {
__bss_start__ = .;
*(.bss*)
} > ted
__bss_end__ = .;
__bss_size__ = __bss_end__ - __bss_start__;
}
of course change the addresses as you see fit. clearly the names of the sections mean nothing, you might try rom and ram if it helps you.
If you were to do something like this
MEMORY
{
rom : ORIGIN = 0x80000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.bss : { *(.bss*) } > ram
.rodata : { *(.rodata*) } > rom
.data : { *(.data*) } > ram
}
and then objcopy it then you are going to end up with a huge file, even if you only have one instruction of .text and one byte of data. because a binary format has to cover everything as in the memory so it will make a file that is 0x80000000+sizeof(text)-0x20000000. If you had one instruction only in ram and one byte of data then the file would be 0x60000004 bytes or 1.6 gig. leave it as an elf and use objdump -D until you have your linker script worked out THEN make a .bin if you really need one. or make an intel hex or s record and again you can examine it to see if it is all in the same address space before trying a binary.
the first key to your problem is the AT
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.data : { *(.data*) } > ted AT > bob
.bss : { *(.bss*) } > bob
}
it says I want the .data in the address space ted, but place it in the binary in the bob space. it compiles for the ted address space, but the bits are loaded from bob space. exactly what you want. except you dont know how much .data to copy from rom to ram. you have to be super careful where you place the variables in the more complicated one or it wont work right.

How to place kernel_entry function beginning of the text section

My Kernel text section starts at address 0x80100000 & kernel_entry function is at the address 0x80585f70. I want kernel_entry should place beginning of the text section at address 0x80100000.
Starting address of text section
$ head -n 10 ../../../System.map
80100000 A _text
80100400 T __kernel_entry
80100400 T _stext
Entry point address in initrd image
$ readelf -h vmlinuz-initrd
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: MIPS R3000
Version: 0x1
Entry point address: 0x80585f70
Start of program headers: 52 (bytes into file)
Start of section headers: 63759852 (bytes into file)
Flags: 0x50001001, noreorder, o32, mips32
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 2
Size of section headers: 40 (bytes)
Number of section headers: 25
Section header string table index: 22
$
I tried to solve the problem using linux linker script(vmlinux.lds) by adding head.o module at the beginning of text section.
vmlinux.lds linux linker script
SECTIONS
{
. = 0x80100000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
. = ALIGN(8); head.o(.ref.text)
. = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.cpuinit.text) *(.cpuexit.text) *(.text.unlikely)
. = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
. = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
. = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
kernel_entry function format .ref.text
$ objdump -t head.o
head.o: file format elf32-little
SYMBOL TABLE:
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l d .ref.text 00000000 .ref.text
00000000 l d .cpuinit.text 00000000 .cpuinit.text
00000000 l d .reginfo 00000000 .reginfo
00000000 l d .pdr 00000000 .pdr
00000400 g O .text 00000000 _stext
00000400 g F .text 00000000 __kernel_entry
00000000 g F .ref.text 000000c8 kernel_entry
But I am unable to change this due to vmlinux.lds automatically generated through vmlinux.lds.S file.
I tried to put the same line ". = ALIGN(8); head.o(.ref.text)"
from vmlinux.lds but head.o is not found while building the kernel.
Please help me how can I resolve the problem.
First of all, if you insert a symbol into a section, then all subsequent symbols in the section are shifted. If some code relies on the assumption that there is a certain object located at .text + hardcoded offset then the code will be broken.
If you decided to do that anyway, then you need to be able to edit the vmlinux.lds.S file or at least patch the generated vmlinux.lds before the linking stage. The vmlinux.lds.S is essentially a linker script + a bunch of C macros, so the syntax is pretty similar.
The basic idea is to put the kernel_entry() to a separate section called .kernel_entry and add a record to the vmlinux.lds.S that puts .kernel_entry to the .text before all other sections. The separate section is needed to ensure there is only one symbol in the section.
How to do that:
Put the kernel_entry() function to its own section. You may do that in the source code with a GCC attribute like this:
void kernel_entry() __attribute__((section(".kernel_entry")));
{
/* function body */
}
Put the .kernel_entry section to beginning of the .text in vmlinux.lds.S.
Each architecture has its own vmlinux.lds.S, so the exact contents of the file may be different, but in general, you may find a .text section defined in vmlinux.lds.S like this:
.text : {
TEXT_TEXT
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
IRQENTRY_TEXT
*(.text.*)
} :text = 0
The TEXT_TEXT, SCHED_TEXT and so on are actually C macros that expand to some linker script commands. You may use the regular syntax and add *(.kernel_entry) to the beginning of the section, like this:
.text : {
*(.kernel_entry)
TEXT_TEXT
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
IRQENTRY_TEXT
*(.text.*)
} :text = 0
That is it. The linker will put .kernel_entry (containing kernel_entry()) to the beginning of the .text which is seems to be what you want.
Good luck!

memory allocation in data/bss/heap and stack

I have the following piece of code:
#include <stdio.h>
int global_var;
int global_initialized_var=5;
void function(){
int stack_var;
printf("The function's stack_var is at address 0x%08x\n", &stack_var);
}
int main(){
int stack_var;
static int static_initialized_var = 5;
static int static_var;
int *heap_var_ptr;
heap_var_ptr = (int *) malloc(4);
// Next variables will be at data segment
printf("global_initialized_var is at address 0x%08x\n", &global_initialized_var);
printf("static_initialized_var is at address 0x%08x\n\n", &static_initialized_var);
// These will be in the bss segment
printf("static_var is at address 0x%08x\n", &static_var);
printf("global_var is at address 0x%08x\n", &global_var);
// This will be in heap segment
printf("heap_var is at address 0x%08x\n\n", heap_var_ptr);
// These will be in stack segment
printf("stack_var is at address 0x%08x\n", &stack_var);
function();
}
I am getting back the following:
# ./memory_segments
global_initialized_var is at address 0x0804a018
static_initialized_var is at address 0x0804a01c
static_var is at address 0x0804a028
global_var is at address 0x0804a02c
heap_var is at address 0x09285008
stack_var is at address 0xbf809fbc
The function's stack_var is at address 0xbf809f8c
It is supposed that the first 2 variables because they are initialized static and global should be in the .data segment where the other 2 static_var and global_var should be in .bss segment. The addresses that I am getting I think imply that both of them are in the same memory region. I would do a blind guess and I would say that this is the .bss segment.
Anyway the question is the following, am I right ?? And if I am how is it possible to find out where are the "limits" of these regions (bss, data, etc) or from where they are starting etc.
Assuming you are compiling with something like gcc memaddr.c -g -o memaddr, you can use objdump -h to display size and address of your sections:
$ objdump -h memaddr | grep -e 'Size' -e '\.data' -e '\.bss'
Idx Name Size VMA LMA File off Algn
23 .data 00000018 0000000000601018 0000000000601018 00001018 2**3
24 .bss 00000018 0000000000601030 0000000000601030 00001030 2**3
$
Also you can use objdump -t to display addresses and sections your symbols belong in:
$ objdump -t memaddr | grep "_var"
000000000060102c l O .data 0000000000000004 static_initialized_var.2049
0000000000601040 l O .bss 0000000000000004 static_var.2050
0000000000601044 g O .bss 0000000000000004 global_var
0000000000601028 g O .data 0000000000000004 global_initialized_var
$
So we can see that the .data and .bss sections are fairly small and happen to lie next to each other, so it is not surprising the .data and .bss addresses are so close.

Resources