I am a newbie in writing bootloaders. I have written a helloworld bootloader in asm, and
I am now trying to write one in C. I have written a helloworld bootloader in C, but I cannot compile it.
This is my code. What am I doing wrong? Why won't it compile?
void print_char();
int main(void){
char *MSG = "Hello World!";
int i;
__asm__(
"mov %0, %%SI;"
:
:"g"(MSG)
);
for(i=0;i<12;i++){
__asm__(
"mov %0, %%AL;"
:
:"g"(MSG[i])
);
print_char();
}
return 0;
}
void print_char(){
__asm__(
"mov $0X0E, %AH;"
"mov $0x00, %BH;"
"mov $0x04, %BL;"
"int $0x10"
);
}
Let me assume a lot of things here: you want to run your bootloader on an x86 system, you have the gcc toolchain set up on a *nix box.
There are some points to be taken into account when writing a bootloader:
the 510 byte limit for a VBR, even lesser for MBR due to partition table (if your system needs one)
real mode - 16 bit registers and seg:off addressing
bootloader must be flat binary that must be linked to run at physical address 7c00h
no external 'library' references (duh!)
now if you want gcc to output such a binary, you need to play some tricks with it.
gcc by default splits out 32bit code. To have gcc output code that would run in real mode, add __asm__(".code16gcc\n") at the top of each C file.
gcc outputs compiled objects in ELF. We need a bin that is statically linked at 7c00h. Create a file linker.ld with following contents
ENTRY(main);
SECTIONS
{
. = 0x7C00;
.text : AT(0x7C00)
{
_text = .;
*(.text);
_text_end = .;
}
.data :
{
_data = .;
*(.bss);
*(.bss*);
*(.data);
*(.rodata*);
*(COMMON)
_data_end = .;
}
.sig : AT(0x7DFE)
{
SHORT(0xaa55);
}
/DISCARD/ :
{
*(.note*);
*(.iplt*);
*(.igot*);
*(.rel*);
*(.comment);
/* add any unwanted sections spewed out by your version of gcc and flags here */
}
}
write your bootloader code in bootloader.c and build the bootloader
$ gcc -c -g -Os -march=i686 -ffreestanding -Wall -Werror -I. -o bootloader.o bootloader.c
$ ld -static -Tlinker.ld -nostdlib --nmagic -o bootloader.elf bootloader.o
$ objcopy -O binary bootloader.elf bootloader.bin
Since you already have built boot loaders with ASM, I guess the rest is obvious to you.
-
taken from my blog: http://dc0d32.blogspot.in/2010/06/real-mode-in-c-with-gcc-writing.html
A bootloader is written in ASM.
When compiling C code (or C++, or whatever), a compiler will 'transform' your human readable code into machine code. So you can't be sure about the result.
When a PC boots, the BIOS will execute code from a specific address.
That code needs to be executable, directly.
That's why you'll use assembly.
It's the only way to have un-altered code, that will be run as written, by the processor.
If you want to code in C, you'll still have to code an ASM bootloader, which will be in charge to load properly the machine code generated by the compiler you use.
You need to understand that each compiler will generate different machine codes, that may need pre-processing before execution.
The BIOS won't let you pre-process your machine code. The PC boot is just a jump to a memory location, meaning the machine code located at this location will be directly executed.
Since you are using GCC, you should read the info pages about the different "target environments". You most probably want to use the -ffreestanding flag. Also I had to use -fno-stack-protector flags to avoid some ugly magic of the compiler.
Then, you will get linker errors saying that memset and the like are not found. So you should implement your own version of these and link them in.
I tried this a few years ago -- options may have changed.
You have to run gcc with -ffreestanding (don't link) and then link using ld with the flags -static, -nostdlib
As far as I know, you cannot write bootloader in C. That is because, C needs you to work in a 32-bit protected mode while in bootloader some portions are in 16-bit mode.
Related
i want to get gcc to compile c-code for me into x86-32 linux binary code, but without any librarys or so around it.
I just want to specify an address at the start, and it should assume it has been loaded there. I will then manually build an elf file from the output by hand and set up everything.
I know how to do something like this using NASM, but i have something more complicated in mind where i don't want to use only assembler. I dont need any librarys, i will use pure syscalls with inline asm. I also do not care much if it looses some portability.
I tried around a bit, but could not find a way to do that.
Can someone not only provide me with the correct settings for that, but also some background on the compile and linker parameters?
I tried searching through the gcc manual, but found it very confusing.
I want to get gcc to compile c-code for me into x86-32 linux binary code, but without any librarys or so around it.
That means you write freestanding C code. (When the standard library is available, you have a hosted environment; when not, a freestanding one. )
To compile e.g. foo.c to an executable, foo, make sure it has a _start() function, and use
gcc -march=i686 -mtune=generic -m32 -ffreestanding -nostdlib -nostartfiles foo.c -o foo
The GNU toolchain uses the address of the _start symbol to encode the start address of the executable in the ELF file.
This answer is an actual real-world example for x86-64. For x86-32 (or any other architecture), you'll need to adjust the SYSCALL_ macros.
In a comment, OP explains they want a binary blob, instead of an ELF executable.
In this case, it is best to tell the compiler to generate a position independent executable. For example, 'blob.c':
void do_something(int arg)
{
/* Do something with arg, perhaps a syscall,
or inline assembly? */
}
void loop_something(int from, int to)
{
int arg;
if (from <= to)
for (arg = from; arg <= to; arg++)
do_something(arg);
else
for (arg = from; arg <= to; arg--)
do_something(arg);
}
void _start(void)
{
loop_something(2, 5);
do_something(6);
loop_something(5, 2);
do_something(1);
}
I do recommend declaring all functions except _start as static, to avoid any global offset table (GOT) or procedure linkage table (PLT) references (like <__x86.get_pc_thunk.bx> calls).
Compile this to an position independent executable using e.g.
gcc -march=i686 -mtune=generic -m32 -O2 -fPIE -ffreestanding -nostdlib -nostartfiles blob.c -o blob
strip it,
strip --strip-all blob
and dump the contents of the binary:
objdump -fd blob
In this output, there are two important lines:
start address 0x08048120
which tells the address of the _start symbol, and
080480e0 <.text>:
which tells the offset of the code, in hexadecimal. Subtract the former from the latter (0x08048120 - 0x080480e0 = 0x40 = 64) to get the offset of the start symbol.
Finally, dump the code into a raw binary file 'blob.raw' using
objcopy -O binary -j .text blob blob.raw
I am receiving the warning after I have added -nostdlib to the linker flags.
tricore/bin/ld.exe: warning: cannot find entry symbol _start; defaulting to c0000000
The linking is done as follows:
$(OUTDIR)/$(BINARY_NAME).elf: $(OUTDIR) $(OBJ)
$(TRICORE_TOOLS)/bin/tricore-gcc -Tld/iRAM.ld -Wl,--no-warn-flags -Wl,
--gc-sections -Wl,-n -nostdlib -o $# $(OBJ) C:\OpenSSL-Win32\lib\MinGW
\libssl-1_1.a C:\OpenSSL-Win32\lib\MinGW\libcrypto-1_1.a
I read that -nostdlib results in not using the standard system startup files or libraries when linking.
The file ld/iRAM.ld looks as follows, and as far as i understand, it contains the _start symbol and it is passed to the linker:
ENTRY(_start)
/*
* Global
*/
/*Program Flash Memory (PFLASH0)*/
__PMU_PFLASH0_BEGIN = 0x80000000;
__PMU_PFLASH0_SIZE = 2M;
/*Program Flash Memory (PFLASH1)*/
........
........
SECTIONS
{
/*Code-Sections*/
/*
* Startup code for TriCore
*/
.startup_code :
{
PROVIDE(__startup_code_start = .);
........
}
.....
}
I have read, that if I pass the -nostdlib flag to the linker, I need to provide the startup code to it too. Does anyone know what I am doing wrong here?
The ENTRY directive in linker script only specifies the name of the entry point symbol (i.e. function). However you still need to actually provide the function with such name in one of your source files.
Most likely solution is to rename your main function to _start function if you have one. Also note that the _start won't have argc and argv parameters since they are usually provided by the standard library. It also should never return since there is nowhere to return to. Instead you'll have to call platform-specific exit function: exit() syscall on Linux or ExitProcess() on Windows. This, however, might not be necessary if you are working in a freestanding environment (i.e. no OS).
I am compiling a static library, which leverages some inline assembly code.
I notice that when I use labels for the jmp instruction:
int foo(){
asm volatile
(
"mov 0x60(%r8),%r11d\n\t"
"jmp *S_401a70\n\t"
...
"S_401a70: xor %rax, %rax\n\t"
...
)
}
and compile the code into a static library with the following flags:
-Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SOME_LIBRARY_PATH) \
-Wl,--whole-archive -l$(SOME_Library_Name) -Wl,--no-whole-archive \
-Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \
-Wl,-pie,-eenclave_entry -Wl,--export-dynamic \
-Wl,--defsym,__ImageBase=0
I would get some errors like:
/usr/bin/ld: Enclave/libtest.o: relocation R_X86_64_32S against `.text' can not be used when making a shared object; recompile with -fPIC
However, since I am compiling into a static library, I don't think -fPIC would make sense. I tried so, but it doesn't work at all.
This seems like an issue with the gcc assembly extension, but I am not sure. Could anyone shed some lights on this? Thank you!
It is not a tool issue. First of all -fPIC affects only C code. And affects it in such way that generated code won't contain absolute addresses of referred data/code and won't rely on its own address in memory (it is a somewhat simplified explanation). Next - it has nothing to do with assembly inlines. Since here code was generated by programmer. And if it is written in a way that introduces absolute addresses or some stuff that introduces dependency on its memory location - compiler can't help with it.
P.S. You may built static library even with position-dependent code but it won't be accepted by linker if someone will try to link it into shared library, since resulting shared library should be position-independent.
Is it possible to create a basic bare-metal Assembly bootup/startup program using only GNU LD command-line options in lieu of a customary -T scriptfile for a Cortex-M4 target?
I have reviewed the GNU LD documentation and searched various locations including this site; however, I have not found any information suggesting that the exclusive use of command-line options for the GNU linker is possible or not possible.
My attempt to manage the object file layout without a customary vendor provided *.ld scriptfile is purely academic. This not homework. I'm not requesting any help for writing the startup Assembly code. I'm merely looking for a definitive answer or further resource direction.
$ arm-none-eabi-ld bootup.o -o bootup #bootup.ld.cli.file
Sample bootup.ld.cli.file content
--entry 0x0
--Ttext=0x0
--section-start .isr_vector=0x0
--section-start _start=0x4
--section-start .MyCode=0x8c
--Tdata=0x20000000
--Tbss=0x20000000
-M=bootup.map
--print-gc-sections
you have your answer right there the -Ttext=number -Tdata=number and so on are no gnu linker script items they are gnu command line items. note the at sign on your command line.
A gnu linker script looks more like this (although most are significantly more complicated even if they dont need to be).
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
Note that the gnu linker is a bit funny when you use the -Ttext=address approach, sometimes it will insert gaps you might have a few Kbytes of program and instead of it just linearly placing it at address like it should it will put some, then pad some dead space, then put some more, never figured out why but for extremely limited targets the linker script (vs command line) all other factors held constant, does not put the gap in the output.
EDIT:
so.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.thumb_func
reset:
b hang
.thumb_func
hang: b .
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.thumb_func
.globl dummy
dummy:
bx lr
flash.ld
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
blinker02.c
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int ra;
for(ra=0;ra<100;ra++) dummy(ra);
return(0);
}
Makefile
ARMGNU = arm-none-eabi
AOPS = --warn -mcpu=cortex-m0
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding -mcpu=cortex-m0
all : blinker02.bin sols.bin socl.bin
clean:
rm -f *.bin
rm -f *.o
rm -f *.elf
rm -f *.list
so.o : so.s
$(ARMGNU)-as $(AOPS) so.s -o so.o
flash.o : flash.s
$(ARMGNU)-as $(AOPS) flash.s -o flash.o
blinker02.o : blinker02.c
$(ARMGNU)-gcc $(COPS) -mthumb -c blinker02.c -o blinker02.o
blinker02.bin : flash.ld flash.o blinker02.o
$(ARMGNU)-ld -o blinker02.elf -T flash.ld flash.o blinker02.o
$(ARMGNU)-objdump -D blinker02.elf > blinker02.list
$(ARMGNU)-objcopy blinker02.elf blinker02.bin -O binary
sols.bin : so.o
$(ARMGNU)-ld -o sols.elf -T flash.ld so.o
$(ARMGNU)-objdump -D sols.elf > sols.list
$(ARMGNU)-objcopy sols.elf sols.bin -O binary
socl.bin : so.o
$(ARMGNU)-ld -o socl.elf -Ttext=0x08000000 -Tbss=0x20000000 so.o
$(ARMGNU)-objdump -D socl.elf > socl.list
$(ARMGNU)-objcopy socl.elf socl.bin -O binary
The difference between the command line and the linker script socl and sols list files are the names
diff sols.list socl.list
2c2
< sols.elf: file format elf32-littlearm
---
> socl.elf: file format elf32-littlearm
Not going to bother with demonstrating the difference you may see down the road.
For assembly only you dont need to worry about the no start files and other command line options (on gcc). With C objects you do. by not allowing the linker to use the as-built/configured toolchains (or lets say C library) bootstrap code, you have to provide one, if you dont complicate the linker script to the point that specific object files are called out then the ordering of objects on the command line matters, if you swap flash.o and blinker02.o on the ld command line in the makefile, the binary wont work. you can set entry points all you want but those are strictly for the loader, if this is bare metal which it appears to be then the entry point is useless, the hardware boots how it boots, in this case with a cortex-m address zero is the value to load in the stack pointer, address four is the address to the reset vector (with the lsbit set since this is a thumb only machine, let the tools do that for you using the gnu assembler specific thumb_func to indicate the next label is a branch destination address).
I sprinkled cortex-m0 about one because that is what I took this code from and two the original armv4t and armv5t or as called out in the newer arm docs "all thumb variants", is the most portable arm instruction set across the arm cores. with your cortex-m4 you can get rid of that or perhaps make it a -m3 or -m4 to pull in the armv7-m thumb2 extensions.
so the short answer is
arm-none-eabi-ld -o so.elf -Ttext=0x08000000 -Tbss=0x20000000 so.o
Is more than adequate for making working binaries ASSUMING you dont need a .data.
.data requires a lot more stuff, linker script, a more complicated bootstrap, etc. That or you do a copy-jump thing, compile the REAL program to be run in sram only (different entry point full sized arm style but at the ram base address), then write an adhoc tool to take that binary and turn it into say .word 0xabcdef entries in a program that copies from flash to ram the whole REAL program then branches, that copy and jump program is now flash only with no .data nor .bss really needed and can use the command line, so can the REAL ram only program. And I probably lost you already on that one.
Likewise, using the command line you cannot or should not assume that .bss is zeroed, your bootstrap has to do that too. Now if you have .bss and no .data, then sure you could blindly zero all of the ram on boot before you branch to your C programs entry point (I use notmain() both because at least one old compiler added unnecessary garbage to the binary if it saw a main() function and to emphasize the point that normally there is nothing magic about the function named main().).
Linker scripts are toolchain specific, so no reason to expect gnu linker scripts to port to Kiel to port to ARM (yes I know ARM owns Kiel now was referring to RVCT or whatever it is now), etc. So that is the first .data/.bss problem. Ideally you want your tools to do the work, so they know how bit .data and .bss are so just let them tell you, how you let them tell you is crafting the linker script right (at least with ld) and that is tricky, but it creates variables if you will that can define things like start address for .bss, end address for .bss maybe even some math to subtract them and get length, likewise for .data, then in the bootstrap assembly language you can zero out the .bss memory using start address and length, and/or start address and end address. For .data you need two addresses, where you put it in flash (more linker script foo) and where it wants to go in ram, and the length then the bootstrap copies.
so basically if you write this code
unsigned int x=5;
unsigned int y;
and you use a command line linker script, there is no reason whatsoever to expect x to be 5 or y to be 0 when the first C function is entered that uses those variables. If you assume that x will be a 5 then your program will fail.
if you do this instead
unsigned int x;
unsigned int y;
void myfun ( void )
{
x=5;
y=0;
}
now those assignments are instructions in .text and not values in .data so it will always work command line or not simple linker script or complicated, etc.
For curiosity's sake, I'm trying to see what's the smallest that I can make a C program with a minimum of assembly language. I want to see if I can make a simple OpenGL demo (i.e. demo scene) using OpenGL and GLUT linked dynamically, without the standard library. However, I'm running into trouble with the most basic stuff.
I've created a test main.c file that contains
void newStart() {
//Do stuff here...
asm("movl $1, %eax;"
"xorl %ebx, %ebx;"
"int $0x80;");
}
and I'm making it with
gcc main.c -nostdlib -e newStart -o min
using the '-e' option as recommended by this StackOverflow question. I get the following error when I try to compile it:
ld: warning: symbol dyld_stub_binder not found, normally in libSystem.dylib
ld: entry point (newStart) undefined. for architecture x86_64
I'm running OS X 10.7 (Lion). Can anyone help me out?
For newStart(), the corresponding symbol is _newStart. You should use that for the -e option:
gcc main.c -nostdlib -e _newStart -o min
See this Stack Overflow question about why underscores are prepended to (extern) function names: Why do C compilers prepend underscores to external names?