Linking multiple files

3

I was doing tests with a couple of files that are planned to be an attempt of script that can be started in bootloader .
In principle to link between the 2 files that made the executable ( kernel.c and loader.asm ) with this command after having compiled both.

ld -m elf_i386 -T linker.ld -o kernel kasm.o kc.o

With kc.o the file kernel.c compiled and kasm.o the file loader.asm compiled, both compiled following the following commands:

gcc -m32 -ffreestanding -c kernel.c -o kc.o 

nasm -f elf32 loader.asm -o kasm.o

Then I decided to add a support file to the combination, adding kernelutils.c as support file for kernel.c . How should I propose linking to achieve the executable? How should I proceed?

kernel.c

/*
 *
 * kernel.c - version 0.0.1
 * This script is under the license of the distributed package, this license
 * can be found in the package itself
 * Script coded by Cristian Simón for the CKA Proyect
 * ----
 * License: GNU GPL v3
 * Coder: Cristian Simón
 * Proyect: CKA 
 * 
 */
/* Output defines */
#define BLACK_BGROUND 0X07 /* black background */
#define WHITE_TXT 0x07 /* light gray on black text */
#define GREEN_TXT 0x02 /* light green on black text */
#define RED_TXT 0x04 /* light red on black text*/
#define CYAN_TXT 0x03 /*light cyan on black text */

#include <stddef.h>
#include <stdint.h>
#include <cpuid.h>


void k_main() 
{
    k_clear_screen();
    k_printf(" Wellcome to", 0, WHITE_TXT);
    k_printf(" CKA!", 1, GREEN_TXT);
    k_printf("==============>", 2, WHITE_TXT);
    k_printf(" CKA stands for C Kernel with Assembly", 3, WHITE_TXT);
    k_printf(" Version 0.0.1, => based in the job of Debashis Barman", 4, WHITE_TXT);
    k_printf(" Contact => [email protected] / [email protected]", 5, WHITE_TXT);
    k_printf("           or in the github repository page", 6, WHITE_TXT);
    k_sleep_3sec();
    k_clear_screen();
    /* here start the magic */
    k_printf(" !===> Starting Checkup <===!", 0, WHITE_TXT);
    k_printf(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-", 1, WHITE_TXT);

    k_printf("[KernelInfo] Switched to protected mode successfully", 5, CYAN_TXT);

}

kernelutils.c

/*
 *
 * kernel_util.c
 * This script is under the license of the distributed package, this license
 * can be found in the package itself
 * Script coded by Cristian Simón for the CKA Proyect
 * ----
 * License: GNU GPL v3
 * Coder: Cristian Simón
 * Proyect: CKA 
 * 
 */
#define BLACK_BGROUND 0X07 /* black background */
#define WHITE_TXT 0x07 /* light gray on black text */
#define GREEN_TXT 0x02 /* light green on black text */
#define RED_TXT 0x04 /* light red on black text*/
#define CYAN_TXT 0x03 /*light cyan on black text */

void k_clear_screen();
void k_sleep_3sec();
unsigned int k_printf(char *message, unsigned int line, float color);

/* k_clear_screen : to clear the entire text screen */
void k_clear_screen()
{
    char *vidmem = (char *) 0xC00B8000;
    unsigned int i=0;
    while(i < (80*25*2))
    {
        vidmem[i]=' ';
        i++;
        vidmem[i]=BLACK_BGROUND;
        i++;
    };
}

/* k_printf : the message and the line # */
unsigned int k_printf(char *message, unsigned int line, float color)
{
    char *vidmem = (char *) 0xC00B8000;
    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]=color;
            i++;
        };
    };

    return(1);
}

/* 
* k_sleep_3sec : to make a simple delay of aprox 3 sec, since is a nasty sleep, 
* duration will vary
* from system to system
*/
void k_sleep_3sec()
{
    int c = 1, d = 1;
    for ( c = 1 ; c <= 20000 ; c++ )
    for ( d = 1 ; d <= 20000 ; d++ )
    {}
}  

linker.ld (the script I use for ld)

ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)

SECTIONS {
   /* The kernel will live at 3GB + 1MB in the virtual
      address space, which will be mapped to 1MB in the
      physical address space. */
   . = 0xC0100000;

   .text : AT(ADDR(.text) - 0xC0000000) {
       *(.text)
       *(.rodata*)
   }

   .data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
       *(.data)
   }

   .bss : AT(ADDR(.bss) - 0xC0000000) {
       _sbss = .;
       *(COMMON)
       *(.bss)
       _ebss = .;
   }
}

loader.asm

global _loader                          ; Make entry point visible to linker.
extern k_main                           ; _main is defined elsewhere

; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ  1<<0             ; align loaded modules on page boundaries
MEMINFO     equ  1<<1             ; provide memory map
FLAGS       equ  MODULEALIGN | MEMINFO  ; this is the Multiboot 'flag' field
MAGIC       equ    0x1BADB002     ; 'magic number' lets bootloader find the header
CHECKSUM    equ -(MAGIC + FLAGS)  ; checksum required

; This is the virtual base address of kernel space. It must be used to convert virtual
; addresses into physical addresses until paging is enabled. Note that this is not
; the virtual address where the kernel image itself is loaded -- just the amount that must
; be subtracted from a virtual address to get a physical address.
KERNEL_VIRTUAL_BASE equ 0xC0000000                  ; 3GB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22)  ; Page directory index of kernel's 4MB PTE.


section .data
align 0x1000
BootPageDirectory:
    ; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
    ; All bits are clear except the following:
    ; bit 7: PS The kernel page is 4MB.
    ; bit 1: RW The kernel page is read/write.
    ; bit 0: P  The kernel page is present.
    ; This entry must be here -- otherwise the kernel will crash immediately after paging is
    ; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
    dd 0x00000083
    times (KERNEL_PAGE_NUMBER - 1) dd 0                 ; Pages before kernel space.
    ; This page directory entry defines a 4MB page containing the kernel.
    dd 0x00000083
    times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0  ; Pages after the kernel image.


section .text
align 4
MultiBootHeader:
    dd MAGIC
    dd FLAGS
    dd CHECKSUM

; reserve initial kernel stack space -- that's 16k.
STACKSIZE equ 0x4000

; setting up entry point for linker
loader equ (_loader - 0xC0000000)
global loader

_loader:
    ; NOTE: Until paging is set up, the code must be position-independent and use physical
    ; addresses, not virtual ones!
    mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
    mov cr3, ecx                                        ; Load Page Directory Base Register.

    mov ecx, cr4
    or ecx, 0x00000010                          ; Set PSE bit in CR4 to enable 4MB pages.
    mov cr4, ecx

    mov ecx, cr0
    or ecx, 0x80000000                          ; Set PG bit in CR0 to enable paging.
    mov cr0, ecx

    ; Start fetching instructions in kernel space.
    ; Since eip at this point holds the physical address of this command (approximately 0x00100000)
    ; we need to do a long jump to the correct virtual address of StartInHigherHalf which is
    ; approximately 0xC0100000.
    lea ecx, [StartInHigherHalf]
    jmp ecx                                                     ; NOTE: Must be absolute jump!

StartInHigherHalf:
    ; Unmap the identity-mapped first 4MB of physical address space. It should not be needed
    ; anymore.
    mov dword [BootPageDirectory], 0
    invlpg [0]

    ; NOTE: From now on, paging should be enabled. The first 4MB of physical address space is
    ; mapped starting at KERNEL_VIRTUAL_BASE. Everything is linked to this address, so no more
    ; position-independent code or funny business with virtual-to-physical address translation
    ; should be necessary. We now have a higher-half kernel.
    mov esp, stack+STACKSIZE           ; set up the stack
    push eax                           ; pass Multiboot magic number

    ; pass Multiboot info structure -- WARNING: This is a physical address and may not be
    ; in the first 4MB!
    push ebx

    call  k_main                 ; call kernel proper
    hlt                          ; halt machine should kernel return


section .bss
align 32
stack:
    resb STACKSIZE      ; reserve 16k stack on a uint64_t boundary
    
asked by Rottenheimer2 29.07.2017 в 13:30
source

1 answer

2

The solution, to control the compilation of many files in a simple way, is to create a Makefile file.

Here is one that I have created for you:

# Compilador C
CC=gcc
CFLAGS=-Wall -m32 -ffreestanding

# Ensamblador
AS=nasm
ASFLAGS=-f elf32

# Archivos
ARCHIVOS=kernel.o \
     kernelutils.o \
     loader.o

all: $(ARCHIVOS)
    $(CC) $(CFLAGS) -o kernel -T linker.ld $(ARCHIVOS)

clean:
    rm -f $(ARCHIVOS)

# Regla: archivos .c
%.o: %.c
    $(CC) $(CFLAGS) $(CLIBS) -c $< -o $@

# Regla: archivos .asm
%.o: %.asm
    $(AS) $(ASFLAGS) $< -o $@

It is enough to save this text in a file called Makefile , and leave it in the directory where your code is located.

And execute.

  

make all

Explanation

I will explain section by section, so that you understand in a more clear way why this file.

Header

Here we define the compilers we are going to use for our code, it should be clarified that these are declared and used as shell variables, it is usually a standard format.

# Compilador C
CC=gcc
CFLAGS=-Wall -m32 -ffreestanding

# Ensamblador
AS=nasm
ASFLAGS=-f elf32

Note: We use the parameters -Wall to show the warnings and possible errors. I hope you know why the others you already had, such as -ffreestanding or -m32 .

Files

Here we define with what names our object files will come out, which in some part of your question, you said:

  

With kc.o the compiled kernel.c file and kasm.o the compiled loader.asm file, both compiled by following the following commands:

Here we are defining new file names, and it is easy to learn the new names, since the name will be the same file name, with the .o extension.

That is, kernel.c - > kernel.o , loader.asm - > loader.o , Etc. So it will be easier to identify them in the directory.

# Archivos
ARCHIVOS=kernel.o \
         kernelutils.o \
         loader.o

Saving all these names within a single variable called ARCHIVOS .

all and clean?

The following two rules are perhaps those that are always required in any Makefile file.

all: $(ARCHIVOS)
    $(CC) $(CFLAGS) -o kernel -T linker.ld $(ARCHIVOS)

clean:
    rm -f $(ARCHIVOS)

We had previously talked about the variable ARCHIVOS , we will use it here, to build our program using the rule all and we will use it to clean our directory with the rule clean .

The all rule will do something like the following:

gcc -Wall -m32 -ffreestanding -o kernel -T linker.ld kernel.o kernelutils.o loader.o

While the clean rule will take care of executing the following:

rm -f kernel.o kernelutils.o loader.o

It will simply clean the object files generated during the compilation process.

File rules

We find this pair of blocks at the end.

# Regla: archivos .c
%.o: %.c
    $(CC) $(CFLAGS) $(CLIBS) -c $< -o $@

# Regla: archivos .asm
%.o: %.asm
    $(AS) $(ASFLAGS) $< -o $@

To summarize it in a simple way, what each of them does is, that when asking for the compilation of a .c extension file, compile it and its result is the same file with extension .o. This also works for .asm files, compiles them and returns the same files ending in .o. They just do that.

But, after all, how does it work?

I will summarize it in clearer words.

  • We have a list of files to be generated (Variable: ARCHIVOS )
  • We compile each of the respective files.
  • When all the files are compiled, we link them together as a single executable called kernel.

Note

Currently the code is throwing certain warnings when it is compiling.

nasm -f elf32 loader.asm -o loader.o

Result

  

loader.asm: 48: warning: symbol 'loader': GLOBAL directive after symbol definition is an experimental feature

It is still necessary to make a revision of the possible errors that you may have in the code before doing the linking of the objects.

    
answered by 29.07.2017 / 14:23
source