ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 코드 점프
    프로젝트/운영체제 만들기 2023. 8. 7. 02:12

    글의 참고


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.

    - `글의 참조`에서 빨간색 볼드체로 체크된 링크는 이 글을 작성하면 가장 많이 참조한 링크다.

    - `운영체제 만들기` 파트에서 퍼온 모든 참조 글들과 그림은 반드시 `이 글과 그림을 소스 코드로 어떻게 구현을 해야할까` 라는 생각으로 정말 심도있게 잠시 멈춰서 생각해봐야 실력이 발전한다.


    글의 내용

    : 서로 다른 파일간에 코드를 연결하고 싶다. 즉, pri-boot.S와 sec-boot.S가 있을 때, pri-boot.S후에, sec-boot.S가 실행되게 하고 싶다. 혹은 boot.S와 main.c가 있을 때, boot.S 후에 main.c 파일의 main() 함수를 호출하고 싶다. 어떻게 해야 할 까?

     

     

    - x86

    : 첫 번째는 cat 명령어를 이용하는 방법이 있다. 먼저 2개의 서로 다른 언어로 작성된 파일 모두 순수 바이너리 파일로 빌드한다. 그리고 cat 명령어를 통해서 여러 바이너리를 하나의 바이너리로 통합한다. 그런데, 이 때, 순서가 굉장히 중요하다. 어떤 순서로 결합시키기냐에 따라 동작이 완전 달라지므로, 순서에 주의한다.

     

    : cat을 통해 하나의 바이너리가 만들어지면. 이걸 실행시키면 된다. 2개의 빌드 된 바이너리들이 모두 순수 바이너리라면, 코드와 데이터를 제외한 아무 내용도 없을 것이다. 그래서 중간에 점프 명령어가 없다는 전제로 하면, 앞에 바이너리가 모두 실행되고 나면 자연스럽게 뒤에 바이너리가 실행된다. 링커를 사용해도 되지 않을 정도로 아주 심플하다. 

     

     

    : 두 번째는 링커로 2개의 파일을 서로 연결시키고, jmp 및 call 명령을 통해 다른 파일의 엔트리 포인트로 점프하는 방법이다. 별도의 링커스크립트를 작성해야 해서 조금 복잡하지만, 이게 정성적인 방법이다. 주의할 점은 C 메인 함수를 전역으로 사용할 수 있게 놓아야 한다는 것이다. 그렇지 않으면, 부트 로더 파일에서 `call main` 시에 에러가 발생할 수 있다.

     

    : 그런데 리얼 모드에서 굳이 링커를 쓸 필요가 있을까? 링커 스크립트에 작성하는 주소들은 가상 주소다. 아래의 내용을 보자.

    SECTIONS
    {
        .text 0x7C00: 
        {   
            pbl.o (.text)
            sbl.o (.text)
            *(.rodata)
        }   
                                                                                   
        .data :
        {   
            *(.data)
        }   
    
        .bss :
        {   
            *(.bss)
            *(COMMON)
        }   
    }

    : 위와 같이 작성된 링커 스크립트를 통해 sbl.c와 pbl.c 빌드 했다. 빌드 과정은 nasm을 통해 *.asm 파일을 *.o 파일로 만든 뒤, 링커를 통해 *.o 들을 링크시켜 하나의 *.elf 실행 파일을 만들었다. 그리고 *.elf를 objcopy를 통해 순수 바이너리만 추출했다. 

     

    -rwxrwxr-x 1 yohda yohda   872 Jun  8 15:23 bl.bin

    : 해당 파일은 872 사이즈가 된다. 해당 파일의 내용을 `xxd` 명령어로 확인해보자.

     

    $ xxd -l 0x400 bl.bin
    00000000: ea05 7c00 00fa 31c0 8ed8 a3c1 7cb8 00b8  ..|...1.....|...
    00000010: 8ec0 b800 008e d0bd c007 89ec 31f6 26c7  ............1.&.
    00000020: 0400 0f83 c602 81fe a00f 75f2 68ea 7ce8  ..........u.h.|.
    00000030: 5900 83c4 0206 57b8 c007 8ec0 bfbe 0126  Y.....W........&
    00000040: 8a05 3c08 740d 83c7 1081 fffe 0175 f05f  ..<.t........u._
    00000050: 07eb 0d68 0c7d e832 0083 c402 0657 5f07  ...h.}.2.....W_.
    00000060: 06b8 e007 8ec0 31db b402 b020 b500 b102  ......1.... ....
    00000070: b600 cd13 073c 2074 0068 c37c e80c 0083  .....< t.h.|....
    00000080: c402 ff36 c17c ea00 7e00 0055 89e5 5053  ...6.|..~..U..PS
    00000090: 5657 06b8 00b8 8ec0 b8a0 00f7 26c1 7c89  VW..........&.|.
    000000a0: c7ff 06c1 7c8b 7604 8a1c 80fb 0074 0b26  ....|.v......t.&
    000000b0: 881d 83c6 0183 c702 ebee 075f 5e5b 585d  ..........._^[X]
    000000c0: c300 0052 6561 6479 2066 6f72 206a 756d  ...Ready for jum
    000000d0: 7069 6e67 2073 6563 6f6e 6461 7279 2062  ping secondary b
    000000e0: 6f6f 746c 6f61 6465 7200 596f 6864 614f  ootloader.YohdaO
    000000f0: 5320 5072 696d 6172 7920 426f 6f74 204c  S Primary Boot L
    00000100: 6f61 6465 7220 5374 6172 7400 5468 6572  oader Start.Ther
    00000110: 6520 6578 6973 7420 6120 6163 7469 7665  e exist a active
    00000120: 2070 6172 7469 7469 6f6e 0000 0000 0000   partition......
    00000130: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000140: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000150: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000170: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000001a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000001b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000001c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000001e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa  ..............U.
    00000200: ea05 7e00 0058 a3dd 7e68 df7e e87a 0083  ..~..X..~h.~.z..
    00000210: c402 06b8 0000 8ec0 268b 1efe 7df7 d08e  ........&...}...
    00000220: c026 8b0e 0e7e 0739 cb75 0968 037f e858  .&...~.9.u.h...X
    00000230: 0083 c402 683e 7fe8 4f00 83c4 0266 31c0  ....h>..O....f1.
    00000240: 8cd8 66c1 e004 6605 bf7e 0000 66a3 d97e  ..f...f..~..f..~
    00000250: 66b8 d77e 0000 662d bf7e 0000 a3d7 7e0f  f..~..f-.~....~.
    00000260: 0116 d77e 0f20 c00c 010f 22c0 ea71 7e08  ...~. ...."..q~.
    00000270: 0066 b810 008e d88e c08e e08e e88e d0bd  .f..............
    00000280: 007c 0000 bc00 7c00 0055 89e5 5053 5657  .|....|..U..PSVW
    00000290: 06b8 00b8 8ec0 b8a0 00f7 26dd 7e89 c7ff  ..........&.~...
    000002a0: 06dd 7e8b 7604 8a1c 80fb 0074 0b26 881d  ..~.v......t.&..
    000002b0: 83c6 0183 c702 ebee 075f 5e5b 585d c300  ........._^[X]..
    000002c0: 0000 0000 0000 00ff ff00 0000 9acf 00ff  ................
    000002d0: ff00 0000 92cf 0000 0000 0000 0000 0059  ...............Y
    000002e0: 6f68 6461 4f53 2053 6563 6f6e 6461 7279  ohdaOS Secondary
    000002f0: 2042 6f6f 7420 4c6f 6164 6572 2053 7461   Boot Loader Sta
    00000300: 7274 0046 6f72 2065 6e74 6572 696e 6720  rt.For entering 
    00000310: 746f 2070 726f 7465 6374 6564 206d 6f64  to protected mod
    00000320: 652c 2070 7265 7061 7269 6e67 2066 6f72  e, preparing for
    00000330: 2074 6865 2047 6174 652d 4132 3000 5374   the Gate-A20.St
    00000340: 6172 7420 7072 6570 6172 696e 6720 666f  art preparing fo
    00000350: 7220 4744 5420 6f66 2070 726f 7465 6374  r GDT of protect
    00000360: 6564 206d 6f64 6500                      ed mode.

    : 보다시피 만들어진 순수 바이너리는 0번지 부터 저장되어 있다. 그러나, 링커 스크립트 상에서 우리가 작성한 텍스트 섹션의 위치는 0x7C00 이어야 하는데, 0번지부터 되있는 것이 이상하다. 이유는 링커 스크립트에 작성된 주소는 가상 주소이기 때문이다. 그런데, 아래의 링커 스크립트를 통해 링킹을 하면 바이너리의 파일 사이즈가 32616으로 늘어나고, 실제 우리가 원하던 위치(0x7C00)에 바이너리가 나타난다. 

     

    SECTIONS
    {
        .text :
        {                                         
            . = 0x7C00;
            pbl.o (.text)
            sbl.o (.text)
            *(.rodata)
        }
        ...
        ...
    }

     

     

     

    - ARM 

    : 아래의 코드는 라즈베리파이3B의 베어 메탈 코드이다. 파일은 순서대로 start.S, main.c, link.ld, Makefile 이다. 아래의 start.S에서 main.c의 main() 함수를 호출하는데, 이게 어떻게 호출이 가능한 것인지 모르겠다. start.S 파일에 어디에도 main에 대한 언급이 없다. 참고로, bl 이라는 명령은 x86의 jmp 명령어와 유사한 기능을 한다. 즉, 특정 주소로 이동을 하는 것인데, `bl main` 라고 작성하면 main 이라는 심볼의 위치로 이동한다는 뜻이 된다. 그런데 Makefile 및 링크 스크립트를 모두 확인해봐도, start.S에서 main이라는 심볼을 어떻게 찾는지 이해가 가지 않는다.

    .section ".text.boot"
    
    .global _start
    
    _start:
        // read cpu id, stop slave cores
        mrs     x1, mpidr_el1
        and     x1, x1, #3
        cbz     x1, 2f
        // cpu id > 0, stop
    1:  wfe
        b       1b
    2:  // cpu id == 0
    
        // set top of stack just before our code (stack grows to a lower address per AAPCS64)
        ldr     x1, =_start
        mov     sp, x1
    
        // clear bss
        ldr     x1, =__bss_start
        ldr     w2, =__bss_size
    3:  cbz     w2, 4f
        str     xzr, [x1], #8
        sub     w2, w2, #1
        cbnz    w2, 3b
    
        // jump to C code, should not return
    4:  bl      main
        // for failsafe, halt this core too
        b       1b
    void main()
    {
        while(1);
    }
    SECTIONS
    {
        . = 0x80000;
        .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
        .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
        PROVIDE(_data = .);
        .data : { *(.data .data.* .gnu.linkonce.d*) }
        .bss (NOLOAD) : {
            . = ALIGN(16);
            __bss_start = .;
            *(.bss .bss.*)
            *(COMMON)
            __bss_end = .;
        }
        _end = .;
    
       /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
    }
    __bss_size = (__bss_end - __bss_start)>>3;
    SRCS = $(wildcard *.c)
    OBJS = $(SRCS:.c=.o)
    CFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
    
    all: clean kernel8.img
    
    start.o: start.S
    	aarch64-elf-gcc $(CFLAGS) -c start.S -o start.o
    
    %.o: %.c
    	aarch64-elf-gcc $(CFLAGS) -c $< -o $@
    
    kernel8.img: start.o $(OBJS)
    	aarch64-elf-ld -nostdlib -nostartfiles start.o $(OBJS) -T link.ld -o kernel8.elf
    	aarch64-elf-objcopy -O binary kernel8.elf kernel8.img
    
    clean:
    	rm kernel8.elf *.o >/dev/null 2>/dev/null || true
    
    run:
    	qemu-system-aarch64 -M raspi3b -kernel kernel8.img -d in_asm

     

     

     

    - 인자 전달

    : 서로 다른 파일로 코드를 점프할 때, 인자를 전달해주고 싶다. 2가지 방법이 있다.

    1" 레지스터를 통한 전달
    2" 공유 메모리를 통한 전달

    : 2개 모두 좋다. 64비트부터는 레지스터의 개수가 많아져서 레지스터를 통한 인자 전달을 하는 것 같다. 그리고 대개는 레지스터가 속도가 빨라서 해당 방식을 사용한다. 그러나 공유 메모리도 나쁘지 않다. 그런데, 이 방식은 서로 어디 위치에 값을 쓸지 공유하고 있어야 한다. 그래서 가장 편한 방법은 `스택`을 이용하면 좋다. 스택은 대개 각 모드의 진입 시에 초기화를 진행한다. 그래서, 모드 전환전에 스택에 인자를 푸쉬하고, 모드 변경 후 스택을 새로운 메모리 맵에 맞춰 초기화하기 전에 팝하여 인자를 전달 받을 수 있다. 

     

     

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    PIC  (0) 2023.08.07
    함수 호출 규약  (0) 2023.08.07
    BIOS  (0) 2023.08.07
    [운영체제 만들기] ATA / IDE  (0) 2023.08.07
    [운영체제 만들기] Lazy Buddy Allocator  (0) 2023.08.07
Designed by Tistory.