I am following the tutorial from https://github.com/chipsetx/Simple-Kernel-in-C-and-Assembly. I am building it on macOS 12.4 using virtualised Ubuntu. I cannot get the kernel to compile and receive the unrecognised emulation mode: elf_i386 error.
GCC command does not work with -m32 option, but runs after I remove it.
The commands that I used are at the bottom of this post.
Thanks for help.
kernel.asm
;;kernel.asm
bits 32 ;nasm directive
section .text
;multiboot spec
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kmain ;kmain is defined in the c file
start:
cli ;block interrupts
call kmain
hlt ;halt the CPU
kernel.c
#define WHITE_TXT 0x07 /* light gray on black text */
void k_clear_screen();
unsigned int k_printf(char *message, unsigned int line);
/* simple kernel written in C */
void k_main()
{
k_clear_screen();
k_printf("Hello, world! Welcome to my kernel.", 0);
};
/* k_clear_screen : to clear the entire text screen */
void k_clear_screen()
{
char *vidmem = (char *) 0xb8000;
unsigned int i=0;
while(i < (80*25*2))
{
vidmem[i]=' ';
i++;
vidmem[i]=WHITE_TXT;
i++;
};
};
/* k_printf : the message and the line # */
unsigned int k_printf(char *message, unsigned int line)
{
char *vidmem = (char *) 0xb8000;
unsigned int i=0;
i=(line*80*2);
while(*message!=0)
{
if(*message=='\n') // check for a new line
{
line++;
i=(line*80*2);
*message++;
} else {
vidmem[i]=*message;
*message++;
i++;
vidmem[i]=WHITE_TXT;
i++;
};
};
return(1);
}
linker.ld
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : {*(.text)}
.data : {*(.data)}
.bss : {*(.bss)}
}
The commands I have used to build the project.
nasm -f elf32 kernel.asm -o kasm.o => WORKS
gcc -m32 -c kernel.c -o kc.o
=> unrecognized command line option -m32 (works when I remove it)
ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o
=> ERROR HERE: ld: unrecognised emulation mode: elf_i386
qemu-system-i386 -kernel kernel
Related
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'm using gcc to compile a printing function. But met the following 2 problems:
Whenever I apply a local array type variable, the linker would report and error of "undefined reference to `__stack_chk_fail'"
When I'm using "inline" to define a function, it report an error of "undefined reference to `strlen'"
My platform is Ubuntu 18.04, and gcc version is 7.3.0.
I present some of the problem codes:
In function "number()" I applied an "char" type array variable, which actually cause the 1st error:
char* number(char *str, long num, int base, int field_width, int precision, int type)
{
char *TempDgt = DigitsCptl, c, sign;
int i;
char TempStr[50];
....
return str;
}
The problem lies on "char TempStr[50];" and with the removal of it, the link succeed. Similar problem appears on "va_list args;" of the following code:
int color_printk(unsigned int FRcolor, unsigned int BKcolor, char * fmt, ...)
{
int count, i, line;
va_list args;
...
return i;
}
Second problem lies on function "strlen" as follow:
inline int strlen(char * String)
{
...
return __res;
}
This time the "inline" descriptor caused the problem.
Both of the problems occur at "ld" instruction, the following is my makefile:
Kernel:
# Clear previous files
- rm head.s head.o main.o system kernel.bin printk.o
# Compile head.S
gcc -E ./OSFiles/Codes/head.S > head.s
as --64 -o head.o head.s
# Compile main function
gcc -mcmodel=large -fno-builtin -m64 -c ./OSFiles/Codes/main.c
gcc -mcmodel=large -fno-builtin -m64 -c ./OSFiles/Codes/printk.c
# Link compiled files
ld -b elf64-x86-64 -z muldefs -o system head.o main.o printk.o -T ./OSFiles/Codes/Kernel.lds
# Dump out Kernel.bin
objcopy -I elf64-x86-64 -S -R ".eh_frame" -R ".comment" -O binary system Kernel.bin
And the .ids file:
OUTPUT_FORMAT("elf64-x86-64","elf64-x86-64","elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SECTIONS
{
. = 0xffff800000000000 + 0x100000;
.text :
{
_text = .;
*(.text)
_etext = .;
}
.data :
{
_data = .;
*(.data)
_edata = .;
}
.bss :
{
_bss = .;
*(.bss)
_ebss = .;
}
_end = .;
}
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.
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?
I'm trying to get a 'hello world' type program running on my Beagleboard-xm rev. C, by calling a C puts function from assembly.
So far I've been using this as a reference: http://wiki.osdev.org/ARM_Beagleboard
Here's what I have so far, but there's no output.
hello.c
volatile unsigned int * const UART3DR = (unsigned int *)0x49020000;
void puts(const char *s) {
while(*s != '\0') {
*UART3DR = (unsigned int)(*s);
s++;
}
}
void hello() {
puts("Hello, Beagleboard!\n");
}
boot.asm
.global start
start:
ldr sp, =stack_bottom
bl hello
b .
linker.ld
ENTRY(start)
MEMORY
{
ram : ORIGIN = 0x80200000, LENGTH = 0x10000
}
SECTIONS
{
.hello : { hello.o(.text) } > ram
.text : { *(.text) } > ram
.data : { *(.data) } > ram
.bss : { *(.bss) } > ram
. = . + 0x5000; /* 4kB of stack memory */
stack_bottom = .;
}
Makefile
ARMGNU = arm-linux-gnueabi
AOPS = --warn --fatal-warnings
COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding
boot.bin: boot.asm
$(ARMGNU)-as boot.asm -o boot.o
$(ARMGNU)-gcc-4.6 -c $(COPS) hello.c -o hello.o
$(ARMGNU)-ld -T linker.ld hello.o boot.o -o boot.elf
$(ARMGNU)-objdump -D boot.elf > boot.list
$(ARMGNU)-objcopy boot.elf -O srec boot.srec
$(ARMGNU)-objcopy boot.elf -O binary boot.bin
Using just the asm file like this works.
.equ UART3.BASE, 0x49020000
start:
ldr r0,=UART3.BASE
mov r1,#'c'
Here are some Beagleboard/minicom related info: http://paste.ubuntu.com/829072/
Any pointers? :)
I also tried
void hello() {
*UART3DR = 'c';
}
I'm using minicom and send the file via ymodem, then I try to run it with:
go 0x80200000
Hardware and software control flow in minicom are off.
that should have worked for you. Here is some code I dug up from way back when, did not try it on a beagleboard tonight just made sure it compiled, it had worked at one time...
startup.s:
.code 32
.globl _start
_start:
bl main
hang: b hang
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.globl GET32
GET32:
ldr r0,[r0]
bx lr
hello.c :
extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
void uart_send ( unsigned char x )
{
while((GET32(0x49020014)&0x20)==0x00) continue;
PUT32(0x49020000,x);
}
void hexstring ( unsigned int d )
{
//unsigned int ra;
unsigned int rb;
unsigned int rc;
rb=32;
while(1)
{
rb-=4;
rc=(d>>rb)&0xF;
if(rc>9) rc+=0x37; else rc+=0x30;
uart_send(rc);
if(rb==0) break;
}
uart_send(0x0D);
uart_send(0x0A);
}
int main ( void )
{
hexstring(0x12345678);
return(0);
}
memmap (linker script):
MEMORY
{
ram : ORIGIN = 0x82000000, LENGTH = 256K
}
SECTIONS
{
ROM : { startup.o } > ram
}
Makefile :
CROSS_COMPILE = arm-none-eabi
AOPS = --warn --fatal-warnings
COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding
all : hello.bin
hello.bin : startup.o hello.o memmap
$(CROSS_COMPILE)-ld startup.o hello.o -T memmap -o hello.elf
$(CROSS_COMPILE)-objdump -D hello.elf > hello.list
$(CROSS_COMPILE)-objcopy hello.elf -O binary hello.bin
startup.o : startup.s
$(CROSS_COMPILE)-as $(AOPS) startup.s -o startup.o
hello.o : hello.c
$(CROSS_COMPILE)-gcc -c $(COPS) hello.c -o hello.o
clean :
rm -f *.o
rm -f *.elf
rm -f *.bin
rm -f *.list
Looks like I just left the stack pointer wherever the bootloader had it. Likewise, as you, assumed the bootloader had initialized the serial port.
I assume you have serial port access working, you see uboot and you are able to type commands in order to download this program (xmodem, or whatever) into the boards ram? If you cant do that then it may be you are not connected to the serial port right. the beagleboards serial port is screwy, might need to make your own cable.
You can't just blindly write a string of characters to a UART - you need to check status on each character - it works in the single character example because the UART is always going to be ready for the first character, but for the second and subsequent characters you need to poll (or better yet use an ISR, but let's walk before we run).
There's some good example code here: http://hardwarefreak.wordpress.com/2011/08/30/some-experience-with-the-beagleboard-xm-part-2/
I've not enough repetation to comment..
But my answere to
Works either way. Now the weird thing is that I can print individual
characters with with uart_send('c') for example, but cannot print
strings print_string(char *str){ while (*str != '\0') uart_send
(*str++); } print_string("Test"); . Any thoughts on this?
is:
You write faster in the output buffer, as UART is able to send..
So you've to check, if the output buffer is empty, before you send a new character.
I've done this in the code on my blog (http://hardwarefreak.wordpress.com/2011/08/30/some-experience-with-the-beagleboard-xm-part-2/)