I have followed some tutorials on the web and created my own kernel. It is booting on GRUB with QEMU succesfully. But I have the problem described in this SO question, and I cannot solve it. I can have that workaround described, but I also need to use global variables, it would make the job easier, but I do not understand what should I change in linker to properly use global variables and inline strings.
main.c
struct grub_signature {
unsigned int magic;
unsigned int flags;
unsigned int checksum;
};
#define GRUB_MAGIC 0x1BADB002
#define GRUB_FLAGS 0x0
#define GRUB_CHECKSUM (-1 * (GRUB_MAGIC + GRUB_FLAGS))
struct grub_signature gs __attribute__ ((section (".grub_sig"))) =
{ GRUB_MAGIC, GRUB_FLAGS, GRUB_CHECKSUM };
void putc(unsigned int pos, char c){
char* video = (char*)0xB8000;
video[2 * pos ] = c;
video[2 * pos + 1] = 0x3F;
}
void puts(char* str){
int i = 0;
while(*str){
putc(i++, *(str++));
}
}
void main (void)
{
char txt[] = "MyOS";
puts("where is this text"); // does not work, puts(txt) works.
while(1){};
}
Makefile:
CC = gcc
LD = ld
CFLAGS = -Wall -nostdlib -ffreestanding -m32 -g
LDFLAGS = -T linker.ld -nostdlib -n -melf_i386
SRC = main.c
OBJ = ${SRC:.c=.o}
all: kernel
.c.o:
#echo CC $<
#${CC} -c ${CFLAGS} $<
kernel: ${OBJ} linker.ld
#echo CC -c -o $#
#${LD} ${LDFLAGS} -o kernel ${OBJ}
clean:
#echo cleaning
#rm -f ${OBJ} kernel
.PHONY: all
linker.ld
OUTPUT_FORMAT("elf32-i386")
ENTRY(main)
SECTIONS
{
.grub_sig 0xC0100000 : AT(0x100000)
{
*(.grub_sig)
}
.text :
{
*(.text)
}
.data :
{
*(.data)void main (void)
}
.bss :
{
*(.bss)
}
/DISCARD/ :
{
*(.comment)
*(.eh_frame)
}
}
What works:
void main (void)
{
char txt[] = "MyOS";
puts(txt);
while(1) {}
}
What does not work:
1)
char txt[] = "MyOS";
void main (void)
{
puts(txt);
while(1) {}
}
2)
void main (void)
{
puts("MyOS");
while(1) {}
}
Output of assembly: (external link, because it is a little long) http://hastebin.com/gidebefuga.pl
If you look at objdump -h output, you'll see that virtual and linear addresses do not match for any of the sections. If you look at objdump -d output, you'll see that the addresses are all in the 0xC0100000 range.
However, you do not provide any addressing information in the multiboot header structure; you only provide the minimum three fields. Instead, the boot loader will pick a good address (1M on x86, i.e. 0x00100000, for both virtual and linear addresses), and load the code there.
One might think that that kind of discrepancy should cause the kernel to not run at all, but it just happens that the code generated by the above main.c does not use the addresses for anything except read-only constants. In particular, GCC generates jumps and calls that use relative addresses (signed offsets relative to the address of the next instruction on x86), so the code still runs.
There are two solutions, first one trivial.
Most bootloaders on x86 load the image at the smallest allowed virtual and linear address, 1M (= 0x00100000 = 1048576). Therefore, if you tell your linker script to use both virtual and linear addresses starting at 0x00100000, i.e.
.grub_sig 0x00100000 : AT(0x100000)
{
*(.grub_sig)
}
your kernel will Just Work. I have verified this fixes the issue you are having, after removing the extra void main(void) from your linker script, of course. To be specific, I constructed an 33 MB virtual disk, containing one ext2 partition, installed grub2 on it (using 1.99-21ubuntu3.10) and the above kernel, and ran the image successfully under qemu-kvm 1.0 (1.0+noroms-0ubuntu14.11).
The second option is to set the bit 16 in the multiboot flags, and supply the five additional words necessary to tell the bootloader where the code expects to be resident. However, 0xC0100000 will not work -- at least grub2 will just freak out and reboot --, whereas something like 0x00200000 does work fine. This is because multiboot is really designed to use virtual == linear addresses, and there may be other stuff already present at the highest addresses (similar to why addresses below 1M is avoided).
Note that the boot loader does not provide you with a stack, so it's a bit of a surprise the code works at all.
I personally recommend you use a simple assembler file to construct the signature, and reserve some stack space. For example, start.asm simplified from here,
BITS 32
EXTERN main
GLOBAL start
SECTION .grub_sig
signature:
MAGIC equ 0x1BADB002
FLAGS equ 0
dd MAGIC, FLAGS, -(MAGIC+FLAGS)
SECTION .text
start:
mov esp, _sys_stack ; End of stack area
call main
jmp $ ; Infinite loop
SECTION .bss
resb 16384 ; reserve 16384 bytes for stack
_sys_stack: ; end of stack
compile using
nasm -f elf start.asm -o start.o
and modify your linker script to use start instead of main as the entry point,
ENTRY(start)
Remove the multiboot stuff from your main.c, then compile and link to kernel using e.g.
gcc -Wall -nostdlib -ffreestanding -fno-stack-protector -O3 -fomit-frame-pointer -m32 -c main.c -o main.o
ld -T linker.ld -nostdlib -n -melf_i386 start.o main.o -o kernel
and you have a good start to work on your own kernel.
Questions? Comments?
Related
For various purposes, I am trying to obtain the address of the ELF header of the main executable without parsing /proc/self/maps. I have tried parsing the link_list chain given by dlopen/dlinfo functions but they do not contain an entry where l_addr points to the base address of the main executable. Is there any way to do this (Standard or not) without parsing /proc/self/maps?
An example of what I'm trying to do:
#include <stdio.h>
#include <elf.h>
int main()
{
Elf32_Ehdr* header = /* Somehow obtain the address of the ELF header of this program */;
printf("%p\n", header);
/* Read the header and do stuff, etc */
return 0;
}
The void * pointer returned by dlopen(0, RTLD_LAZY) gives you a struct link_map *, that corresponds to the main executable.
Calling dl_iterate_phdr also returns the entry for the main executable on the very first execution of callback.
You are likely confused by the fact that .l_addr == 0 in the link map, and that dlpi_addr == 0 when using dl_iterate_phdr.
This is happening, because l_addr (and dlpi_addr) don't actually record the load address of an ELF image. Rather, they record the relocation that has been applied to that image.
Usually the main executable is built to load at 0x400000 (for x86_64 Linux) or at 0x08048000 (for ix86 Linux), and are loaded at that same address (i.e. they are not relocated).
But if you link your executable with -pie flag, then it will be linked-at 0x0, and it will be relocated to some other address.
So how do you get to the ELF header?
2023 Update:
Isn't a simpler method (if relying on undocumented details), just to call dladdr on the l_ld address in the struct link_map, and then use dli_fbase out of that? – Simon Kissane
Indeed it is. Here is much simpler solution:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
int main()
{
void *dyn = _DYNAMIC;
Dl_info info;
if (dladdr(dyn, &info) != 0) {
printf("a.out loaded at %p\n", info.dli_fbase);
}
return 0;
}
gcc -g -Wall -Wextra x.c -ldl && ./a.out
a.out loaded at 0x556433ea0000 # high address here because my GCC defaults to PIE.
gcc -g -Wall -Wextra x.c -ldl -no-pie && ./a.out
a.out loaded at 0x400000
gcc -g -Wall -Wextra x.c -ldl -no-pie -m32 && ./a.out
a.out loaded at 0x8048000
Original 2012 solution:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
int j;
static int once = 0;
if (once) return 0;
once = 1;
printf("relocation: 0x%lx\n", (long)info->dlpi_addr);
for (j = 0; j < info->dlpi_phnum; j++) {
if (info->dlpi_phdr[j].p_type == PT_LOAD) {
printf("a.out loaded at %p\n",
(void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr));
break;
}
}
return 0;
}
int
main(int argc, char *argv[])
{
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}
$ gcc -m32 t.c && ./a.out
relocation: 0x0
a.out loaded at 0x8048000
$ gcc -m64 t.c && ./a.out
relocation: 0x0
a.out loaded at 0x400000
$ gcc -m32 -pie -fPIC t.c && ./a.out
relocation: 0xf7789000
a.out loaded at 0xf7789000
$ gcc -m64 -pie -fPIC t.c && ./a.out
relocation: 0x7f3824964000
a.out loaded at 0x7f3824964000
Update:
Why does the man page say "base address" and not relocation?
It's a bug ;-)
I am guessing that the man page was written long before prelink and pie, and ASLR existed. Without prelink, shared libraries are always linked to load at address 0x0, and then relocation and base address become one and the same.
how come dlpi_name points to an empty string when info refers to the main executable?
It's an accident of implementation.
The way this works, is that the kernel open(2)s the executable and passes the open file descriptor to the loader (in the auxv[] vector, as AT_EXECFD). Everything the loader knows about the executable it gets by reading that file descriptor.
There is no easy way on UNIX to map a file descriptor back to the name it was opened as. For one thing, UNIX supports hard-links, and there could be multiple filenames that refer to the same file.
Newer Linux kernels also pass in the name that was used to execve(2) the executable (also in auxv[], as AT_EXECFN). But that is optional, and even when it is passed in, glibc doesn't put it into .l_name / dlpi_name in order to not break existing programs which became dependent on the name being empty.
Instead, glibc saves that name in __progname and __progname_full.
The loader coud readlink(2) the name from /proc/self/exe on systems that didn't use AT_EXECFN, but the /proc file system is not guaranteed to be mounted either, so that would still leave it with an empty name sometimes.
There is the glibc dl_iterate_phdr() function. I'm not sure it gives you exactly what you want, but that is as close as I know:
"The dl_iterate_phdr() function allows an application to inquire at run time to find out which shared objects it has loaded."
http://linux.die.net/man/3/dl_iterate_phdr
I'm trying to compile and link an assembly and c program, using GCC and GNU Make, but when I run the "make" command it throws an error "couldn't find GCC", even though I have it installed and working correctly, after some fixing the make file now throws an couldn't find make command in line 14!!!
I've already tried to compile it in another machine but it didn't work. And of course I've tried to run regular GCC commands and it worked perfectly! All seems alright with environment variables. If I try to run the commands without make, it throws a linker.ld syntax error, but that I'm assuming it's on me.
Make file:
CC=gcc
TARGET=bookOs
C_FILES=./kernel.c
OBJS=$(C_FILES:.c=.o)
all compile: $(TARGET)
all: finale
.PHONY: all compile clean finale
%.o:
gcc -c $(#:.o=.c) -ffreestanding -fno-exceptions -m32
$(TARGET): $(OBJS)
$(shell nasm -f elf start.asm -o start.o)
gcc -m32 -nostdlib -nodefaultlibs -lgcc start.o $? -T linker.ld -o $(TARGET)
finale:
$(shell cd ~/Desktop/bookOs/)
$(shell cp $(TARGET) ./iso/boot/$(TARGET))
$(shell grub2-mkrescue iso --output=$(TARGET).iso)
clean:
rm -f *.o $(TARGET) $(TARGET).iso
find . -name \*.o | xargs --no-run-if-empty rm
Assembly file:
bits 32
global _start
extern kernel_early
extern main
section .text
align 4
dd 0x1BADB002 ;magic
dd 0x00
dd - (0x1BADB002 + 0x00) ;checksum
_start:
cli
mov esp, stack
call kernel_early
call main
hlt
section .bss
resb 8192
stack:
C File:
static char* const VGA_MEMORY = (char*)0xb8000;
static const int VGA_WIDTH = 80;
static const int VGA_HEIGHT = 25;
void kernel_early(void)
{ }
int main(void) {
const char *str = "Hello world"; unsigned int i = 0;
string position unsigned int j = 0; // place holder for video buffer position
while (str[i] != '\0') {
VGA_MEMORY[j] = str[i];
VGA_MEMORY[j + 1] = 0x07;
i++; j = j + 2;
}
return 0;
}
Link file:
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.bss : { *(.bss) }
}
This is the error it is currently throwing:
make: : Command not found
make: *** [Makefile:14: bookOs] Error 127
I expect that this make file generated an output linking the c file and the assembly file. Thank you in advance!
The commands starting with $(shell are not doing what you might expect. Please read make's documentation:
[...] it takes as an argument a shell command and evaluates to the output of the
command.
Everything after shell is run as a shell command AND THEN the output of this command is interpreted by make as if it was literally in the Makefile. In your case this will be a command. One example from the documentation:
files := $(shell echo *.c)
So the solution is to remove $(shell and the closing parenthesis.
Hint: To see what make will do without actually doing it, call it with option -n.
I've got this code:
char* vidmem = (char*)0xb8000;
int main()
{
vidmem[0] = 'x';
}
but this acts like vidmem is not initalized. if instead i do something like this:
char* vidmem;
int main()
{
vidmem = (char*)0xb8000;
vidmem[0] = 'x';
}
this works perfectly. Why?
I use this lines to compile and link:
gcc -c main.c -o main.o -ffreestanding -fno-exceptions -m64
gcc -m64 -Wl,--build-id=none -static -nostdlib -nodefaultlibs -lgcc main.obj [...] -T linker.ld -o out.bin
using this linker file:
ENTRY(_start)
SECTIONS
{
. = 0x7C00;
.bss :
{
*(.bss);
}
.text :
{
*(.text);
}
}
There is actually some assembly code calling this C file but it should not matter. Am i doing something wrong with gcc? How can i fix it?
You haven't put your data or rodata sections in your linker script. Check your main.o file for what section vidmem is in and make sure you set that up correctly in your script.
Not only sections in the linked file are needed but also the initialization code which will copy the data.
In the linked script you also need to show there to place the data and there the values in the RO memory are stored( after the closing bracket
I am trying to make my own operating system from scratch and am making my own boot loader. I have a function to print a string onto the screen.
Here is some code that I have:
ORG 0x7C00
BITS 16
mov si, msg
call Print
cli
hlt
Print:
lodsb
cmp al, 0
je Done
mov ah, 0Eh
mov bh, 0
int 10h
jmp Print
Done:
ret
msg db 'Hello World!', 0
times 510-($-$$) db 0
dw 0xAA55
This is then compiled with the following command:
nasm -f bin bootloader.asm -o bootloader.bin
The question is, how would I be able to access the print function within C? I know I have to use the extern keyword, but how would I compile this into a binary format file?
Basically you have to run gcc with -ffreestanding (don't link) and then link using ld with the flags -static, -nostdlib.
Creating bootloader in C is not exactly good idea. I'd recommend you to get copy of GRUB and work on top of it. OSDEV wiki has explained this incredibly well.
To sum things up, whenever you'll try to create bootloader in C, use these to compile it:
$ gcc -m16 -c -g -Os -march=i686 -ffreestanding -Wall -Werror -I. -o bootloader.o bootloader.c
$ ld -static -T linker.ld -nostdlib --nmagic -o bootloader.elf bootloader.o
$ objcopy -O binary bootloader.elf bootloader.bin
Second thing, you can't use extern! You didn't set up stack, so C code will probably bail out pretty quickly. C compiler doesn't know in which format do you pass parameters to it, because your function doesn't follow any of usual conventions. Possible linker script:
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);
}
}
Also, GCC is by default emitting 32-bit code - you need to force it to generate 16-bit code using __asm__(".code16gcc\n") or, as suggested in comments, pass -m16 parameter to compilers' commandline.
You can rewrite your function to C (to make it complain any of calling conventions) like so:
void print(const unsigned char * s){
while(*s){
__asm__ __volatile__ ("int $0x10" : : "a"(0x0E00 | *s), "b"(7));
s++;
}
}
And of course, right after .code16gcc, you'd have to jump directly to your bootloader start: __asm__ ("jmpl $0, $main\n");
I'm trying to make a kernel, and I cannot link the C output with the assembly. The ld. I'm getting the error:
unrecognized emulation mode: elf_i386
I'm using Windows 10 professional with the MinGW32 and MSYS. The code I am using:
link.ld
/*
* link.ld
*/
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
kernel.c
/*
* kernel.c
*/
void kmain(void)
{
const char *str = "my first kernel";
char *vidptr = (char*)0xb8000; //video mem begins here.
unsigned int i = 0;
unsigned int j = 0;
/* this loops clears the screen
* there are 25 lines each of 80 columns; each element takes 2 bytes */
while(j < 80 * 25 * 2) {
/* blank character */
vidptr[j] = ' ';
/* attribute-byte - light grey on black screen */
vidptr[j+1] = 0x07;
j = j + 2;
}
j = 0;
/* this loop writes the string to video memory */
while(str[j] != '\0') {
/* the character's ascii */
vidptr[i] = str[j];
/* attribute-byte: give character black bg and light grey fg */
vidptr[i+1] = 0x07;
++j;
i = i + 2;
}
return;
}
kernel.asm
;;kernel.asm
bits 32 ;nasm directive - 32 bit
section .text
global start
extern kmain ;kmain is defined in the c file
start:
cli ;block interrupts
mov esp, stack_space ;set stack pointer
call kmain
hlt ;halt the CPU
section .bss
resb 8192 ;8KB for stack
stack_space:
To Compile and link I use:
nasm -f elf32 kernel.asm -o kasm.o
gcc -m32 -c kernel.c -o kc.o
ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o
I'm Using:
Gcc 4.8.1
Ld 2.25.1
Nasm 2.11.09rc1
Why am I getting this error, and how can I fix it?
The standard MinGW/32 LD linker doesn't output ELF binaries. Preferably you would be using an i686 cross-compiler, but if you're not you may be able to get away with the tips below.
It appears you are using Arjun's Let's Write a Kernel tutorial. If you are following that tutorial you have missed a step to make kernel.asm compatible with the GRUB boot loader and QEMU's -kernel option. Before we start you should read the rest of the tutorial. The following code adds a Multiboot header to kernel.asm to make it GRUB compatible:
;;kernel.asm
bits 32 ;nasm directive - 32 bit
global entry
extern _kmain ;kmain is defined in the c file
section .text
entry: jmp start
;multiboot spec
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd -(0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
start:
cli ;block interrupts
mov esp, stack_space ;set stack pointer
call _kmain
hlt ;halt the CPU
section .bss
resb 8192 ;8KB for stack
stack_space:
Besides adding a header I've also put an entry label in the file and a jmp start to jump over the Multiboot header. I've done this to make it easy to set a breakpoint at 0x100000 in the future if you start debugging.
One other change is that on MinGW, GCC adds an underscore to function names by default. I've changed references to the C function kmain to _kmain. This differs from the Linux convention.
Since the entry point of our code is now entry instead of start I've modified link.ld to be:
/*
* link.ld
*/
OUTPUT_FORMAT(pei-i386)
ENTRY(entry)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
Another important change in the file above is the usage of OUTPUT_FORMAT(pei-i386) . This will output a Portable Executable Image (32-bit) rather than an ELF (which isn't supported).
In order to build the kernel and produce an ELF image from the PEI-I386 we can use these commands:
nasm -f elf32 kernel.asm -o kasm.o
gcc -m32 -c kernel.c -o kc.o -ffreestanding -nostdlib -nostdinc
ld -T link.ld -o kernel kasm.o kc.o -build-id=none
objcopy -O elf32-i386 kernel kernel.elf
The LD command has been modified to not write out the build-id to the executable to avoid the Multiboot header from being shifted outside the first 8k of the executable. The GCC options have been modified to produce freestanding code (without the standard library and includes) using the options -ffreestanding -nostdlib -nostdinc. We use objcopy to convert the PEI-I386 file (kernel) to an ELF32 image called kernel.elf. You will want to be using kernel.elf with GRUB and/or QEMU.