今天我想來點... Hello World!
又是快樂 Debug 的好日子呢!
昨天已經把程式跑起來了,
但又發現了很多奇怪的事情。
HEX Format 每個欄位到底代表什麼?
為什麼 Program Loader 跟 ELF 格式的開始位置對不起來?
真的要 * 16 嗎?
今天就來嘗試解開這些謎團吧!
可惜 HEX Format 的資料太零散,找到的原檔連結也都失效,
只有第二手資訊真的不好整理,
找了很久 02 和 03 格式的用法還不夠確定,
歡迎熟悉的朋友幫忙補充。
因為 Intel 官方和 Wikipedia 上面的運算都沒寫清楚,
我們參考 arm 的文件 HEX File Format,
很明顯地跟程式寫的一樣,每一行都分成 00、02、03、04、05等 Type。
檔案中包含了程式開始位置、資料位置以及內容等資訊。
HEX File 單行格式:
:llaaaatt[dd...]cc
: 每一行 HEX 格式的第一個字元固定都是這個ll 這一行的 [dd...] 有多少 byteaaaa 代表接下來的資料從哪個 address 開始tt 這一行後面的內容代表什麼[dd...] 這一行所帶的資料cc 用來驗證前面內容是否無誤的 checksumHEX File 單行資料的意義是由 tt 欄位決定:
00 Data Record:10 0054 00 130101FD2326810213040103232EA4FC B210 代表有 16 byte 的資料0054 資料位置從 0x0054 開始00 這行的資料為 Data Record130101FD2326810213040103232EA4FC 16 byte 資料01 End-of-File (EOF) Record:00000001FF02 Extended Segment Address Record:02 0000 02 1000 EC02 代表有 2 byte 的資料0000 這邊永遠為 002 這行的資料為 Extended Segment Address1000 8086 segment 是 16-byte alignment03 Start Segment Address Record:04 0000 03 100000B8 3104 代表有 4 byte 的資料0000 這邊永遠為 003 這行的資料為 Start Segment Address Record1000 8086 Code segment (CS)暫存器數值,是 16-byte alignment00B8 8086 Instruction pointer(IP) 暫存器數值04 Extended Address Record:02 0000 04 00FF FB02 代表有 2 byte 的資料0000 這邊永遠為 002 這行的資料為 Extended Address Record00FF 代表最高的兩個 byte offset,在這邊是 0x00FF0000
05 Start Linear Address Record:04 0000 05 08000121 CD04 代表有 4 byte 的資料0000 這邊永遠為 005 這行的資料為 Start Linear Address Record08000121 代表載入 8086 的 EIP 暫存器位置,也就是 32 bit Program Counter以下是這次的 hello.c 編譯出來的 binary.hex
可以看到程式起始位置為 0x1000 * 16 + 0x00B8 = 0x1000B8
Extended Address 為 0x1000 * 16 = 0x100000
//binary.hex
      type
       ||
:020000021000EC                              //extend segment address
:10005400130101FD2326810213040103232EA4FCB2  //data
:10006400232604FE6F0080028327C4FE0327C4FDF9  //data
:100074003307F700B7074000034707002380E70072  //data
:100084008327C4FE938717002326F4FE8327C4FE28  //data
:100094000327C4FDB307F70083C70700E39607FCF3  //data
:1000A40013000000138507000324C1021301010398  //data
:1000B40067800000130101FF23261100232481001F  //data
:1000C40013040101B70701001385C70EEFF05FF8B1  //data
:1000D40093070000138507008320C10003248100D7  //data
:0800E400130101016780000017                  //data
:0D00EC0068656C6C6F20776F726C6421008A        //data
:04000003100000B831                          //start segment address
:00000001FF                                  //end of hex file
這下子終於看懂 /* ? */ 是什麼意思了!
大概是因為原作者沒用到所以不知道這行在幹嘛XD
//RISC-V-TLM by mariusmm
uint32_t code_segment;
code_segment = stol(line.substr(9, 4), nullptr, 16) * 16; /* ? */
研究一下別人的 8086 assembly code 發現 Extended Segment Address 最終會放到 bp 內,
看起來是 Stack Frame 分隔的 Base Pointer,
有興趣的人可以在該檔案搜尋 "Intel-hex file" ,就可以看到了。
也就是說這兩個分別是起始的 Program Counter 和 Stack Pointer 位置,
但這樣一來,Program Memory Layout 又說不通了,
Stack Section 竟然是在比 Text Section 低的位置?
恩....看起來還要再花時間研究一下,
不過夜深了,猴子也累了,今天就先寫到這裡吧!
hello world 寫起來很簡單,
但是執行的時候卻碰到問題:
沒有看到預期的的字元被一個一個寫進指定的 address。
第一個步驟就是把 hello.o 反組譯之後一條一條指令核對!
Function Call 的 JAL 和 JALR 看起來沒什麼問題,
就來看 printToTrace 的內容:
00010054 <printToTrace>:
printToTrace():
   10054:       fd010113                addi    sp,sp,-48
   10058:       02812623                sw      s0,44(sp)
   1005c:       03010413                addi    s0,sp,48
   10060:       fca42e23                sw      a0,-36(s0)
   10064:       fe042623                sw      zero,-20(s0)
   10068:       0280006f                j       10090 <printToTrace+0x3c>
   1006c:       fec42783                lw      a5,-20(s0)
   10070:       fdc42703                lw      a4,-36(s0)
   10074:       00f70733                add     a4,a4,a5
   10078:       004007b7                lui     a5,0x400
   1007c:       00074703                lbu     a4,0(a4)
   10080:       00e78023                sb      a4,0(a5) # 400000 <__global_pointer$+0x3ee707>
   10084:       fec42783                lw      a5,-20(s0)
   10088:       00178793                addi    a5,a5,1
   1008c:       fef42623                sw      a5,-20(s0)
   10090:       fec42783                lw      a5,-20(s0)
   10094:       fdc42703                lw      a4,-36(s0)
   10098:       00f707b3                add     a5,a4,a5
   1009c:       0007c783                lbu     a5,0(a5)
   100a0:       fc0796e3                bnez    a5,1006c <printToTrace+0x18>
   100a4:       00000013                nop
   100a8:       00078513                mv      a0,a5
   100ac:       02c12403                lw      s0,44(sp)
   100b0:       03010113                addi    sp,sp,48
   100b4:       00008067                ret
發現又是 branch 的時候出問題,
它竟然直接跳到下一行 0x10d4 結束 loop,
而不是跳到 0x1006c 執行 loop 內容!
 rdValue: 0x3fff0 immValue: 0x30
current_pc: 0x10b4 target_pc: 0x10d4 JALR 1 0 0 rs1Value: 0x10d4 rs2Value: 0x0 r
但是東看西看,前天改 BNE 的沒問題!
往回追一道指令,發現 LBU 拿到 0,
也就是說 "hello world!" 的位置從第一個 byte 就是 0,
那個 address... 是不是怪怪的啊!0x100ec 看起來就跟 HEX File算出來的 main 起始位置 0x10b8 差很遠,
肯定哪裡怪怪的!
P.S.ADD 數值也怪怪的,不過是 Logger 的問題,先不理它!
current_pc: 0x1068 target_pc: 0x1090 JAL 0 8 0 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x28
current_pc: 0x1090 target_pc: 0x1094 LW 8 12 15 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffec
current_pc: 0x1094 target_pc: 0x1098 LW 8 28 14 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x100ec immValue: 0xffffffdc
current_pc: 0x1098 target_pc: 0x109c ADD 14 15 15 rs1Value: 0x100ec rs2Value: 0x100ec rdValue: 0x100ec immValue: 0x0
current_pc: 0x109c target_pc: 0x10a0 LBU 15 0 15 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x10a0 target_pc: 0x10a4 BNE 15 0 13 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffcc
假設是 Data Section 拿錯位置,0x10b8 要對起來應該是 0x100b8,
看起來是該改回 * 16 的時候了!
//memory.cpp
...
                        case 2: { //Extended segment address
                                extended_address = std::stoul(line.substr(9, 4), nullptr, 16) * 16;
                        }
                        break;
                        case 3: { //Start segment address
                                uint32_t code_segment = stoul(line.substr(9, 4), nullptr, 16) * 16;
...
Bingo!!!
果然是它!
前幾天沒有找到正確資料的報應立刻就來了。
之後好好地把 HEX Format Program Loader 修好吧!
github 頁面 Tag: ITDay30
先在 Bus 裡面加了一個簡單的 console,
使用 Memory Mapped I/O 做法,
只要存取指定的 address space 就是存取指定的 I/O device。
本來打算另外寫一篇完整一點的 External Device 章節,
但時間不太夠,只能之後再補了。
//bus.cpp
...
        switch (addr) {
                case CONSOLE_BASE:
                        std::cout << "console: \'" << *reinterpret_cast<unsigned char*>(&data) << "\'" << std::endl;
                        break;
                default:
                        memory_socket->b_transport(trans, delay);
                        break;
        }
...
//bus.h
...
        enum memoryMapp {
                MEMORY_BASE = 0x000000,
                CONSOLE_BASE = 0x400000,
        };
...
程式非常簡單,
就是把 "hello world!" 依序寫入上面設定的 address。
//test.c
#define CONSOLE (*(unsigned char *)0x400000)
int printToTrace(char* input)
{
        int i=0;
        while(input[i] != '\0') {
                CONSOLE = input[i];
                i++;
        }
}
int main()
{
        printToTrace("hello world!");
        return 0;
}
current_pc: 0x100b8 target_pc: 0x100bc ADDI 2 16 2 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x3fff0 immValue: 0xfffffff0
current_pc: 0x100bc target_pc: 0x100c0 SW 2 1 12 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xc
current_pc: 0x100c0 target_pc: 0x100c4 SW 2 8 8 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x8
current_pc: 0x100c4 target_pc: 0x100c8 ADDI 2 16 8 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x40000 immValue: 0x10
current_pc: 0x100c8 target_pc: 0x100cc LUI 2 0 15 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x10000 immValue: 0x10
current_pc: 0x100cc target_pc: 0x100d0 ADDI 15 12 10 rs1Value: 0x10000 rs2Value: 0x0 rdValue: 0x100ec immValue: 0xec
current_pc: 0x100d0 target_pc: 0x10054 JAL 31 5 1 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x100d4 immValue: 0xffffff84
current_pc: 0x10054 target_pc: 0x10058 ADDI 2 16 2 rs1Value: 0x3ffc0 rs2Value: 0x0 rdValue: 0x3ffc0 immValue: 0xffffffd0
current_pc: 0x10058 target_pc: 0x1005c SW 2 8 12 rs1Value: 0x3ffc0 rs2Value: 0x40000 rdValue: 0x0 immValue: 0x2c
current_pc: 0x1005c target_pc: 0x10060 ADDI 2 16 8 rs1Value: 0x3ffc0 rs2Value: 0x0 rdValue: 0x3fff0 immValue: 0x30
current_pc: 0x10060 target_pc: 0x10064 SW 8 10 28 rs1Value: 0x3fff0 rs2Value: 0x100ec rdValue: 0x0 immValue: 0xffffffdc
current_pc: 0x10064 target_pc: 0x10068 SW 8 0 12 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffec
current_pc: 0x10068 target_pc: 0x10090 JAL 0 8 0 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x28
current_pc: 0x10090 target_pc: 0x10094 LW 8 12 15 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffec
current_pc: 0x10094 target_pc: 0x10098 LW 8 28 14 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x100ec immValue: 0xffffffdc
current_pc: 0x10098 target_pc: 0x1009c ADD 14 15 15 rs1Value: 0x100ec rs2Value: 0x100ec rdValue: 0x100ec immValue: 0x0
current_pc: 0x1009c target_pc: 0x100a0 LBU 15 0 15 rs1Value: 0x68 rs2Value: 0x0 rdValue: 0x68 immValue: 0x0
current_pc: 0x100a0 target_pc: 0x1006c BNE 15 0 13 rs1Value: 0x68 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffcc
current_pc: 0x1006c target_pc: 0x10070 LW 8 12 15 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffec
current_pc: 0x10070 target_pc: 0x10074 LW 8 28 14 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x100ec immValue: 0xffffffdc
current_pc: 0x10074 target_pc: 0x10078 ADD 14 15 14 rs1Value: 0x100ec rs2Value: 0x0 rdValue: 0x100ec immValue: 0x0
current_pc: 0x10078 target_pc: 0x1007c LUI 0 4 15 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x400000 immValue: 0x400
current_pc: 0x1007c target_pc: 0x10080 LBU 14 0 14 rs1Value: 0x68 rs2Value: 0x0 rdValue: 0x68 immValue: 0x0
console: h
...
console: e
...
console: l
...
console: l
...
console: o
...
console:  
...
console: w
...
console: o
...
console: r
...
console: l
...
console: d
...
console: !
current_pc: 0x10080 target_pc: 0x10084 SB 15 14 0 rs1Value: 0x400000 rs2Value: 0x21 rdValue: 0x0 immValue: 0x0
current_pc: 0x10084 target_pc: 0x10088 LW 8 12 15 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0xb immValue: 0xffffffec
current_pc: 0x10088 target_pc: 0x1008c ADDI 15 1 15 rs1Value: 0xc rs2Value: 0x0 rdValue: 0xc immValue: 0x1
current_pc: 0x1008c target_pc: 0x10090 SW 8 15 12 rs1Value: 0x3fff0 rs2Value: 0xc rdValue: 0x0 immValue: 0xffffffec
current_pc: 0x10090 target_pc: 0x10094 LW 8 12 15 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0xc immValue: 0xffffffec
current_pc: 0x10094 target_pc: 0x10098 LW 8 28 14 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x100ec immValue: 0xffffffdc
current_pc: 0x10098 target_pc: 0x1009c ADD 14 15 15 rs1Value: 0x100ec rs2Value: 0x100f8 rdValue: 0x100f8 immValue: 0x0
current_pc: 0x1009c target_pc: 0x100a0 LBU 15 0 15 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x100a0 target_pc: 0x100a4 BNE 15 0 13 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xffffffcc
current_pc: 0x100a4 target_pc: 0x100a8 ADDI 0 0 0 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x100a8 target_pc: 0x100ac ADDI 15 0 10 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x100ac target_pc: 0x100b0 LW 2 12 8 rs1Value: 0x3ffc0 rs2Value: 0x0 rdValue: 0x40000 immValue: 0x2c
current_pc: 0x100b0 target_pc: 0x100b4 ADDI 2 16 2 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x3fff0 immValue: 0x30
current_pc: 0x100b4 target_pc: 0x100d4 JALR 1 0 0 rs1Value: 0x100d4 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x100d4 target_pc: 0x100d8 ADDI 0 0 15 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x100d8 target_pc: 0x100dc ADDI 15 0 10 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
current_pc: 0x100dc target_pc: 0x100e0 LW 2 12 1 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0xc
current_pc: 0x100e0 target_pc: 0x100e4 LW 2 8 8 rs1Value: 0x3fff0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x8
current_pc: 0x100e4 target_pc: 0x100e8 ADDI 2 16 2 rs1Value: 0x40000 rs2Value: 0x0 rdValue: 0x40000 immValue: 0x10
current_pc: 0x100e8 target_pc: 0x0 JALR 1 0 0 rs1Value: 0x0 rs2Value: 0x0 rdValue: 0x0 immValue: 0x0
INVALID: Opcode :0
Illegal Instruction, end simulation!
exception!
Info: /OSCI/SystemC: Simulation stopped by user.
花了 30 天,終於寫出自己的 hello world 了。
最初的最初那篇,就是在哈囉。
只是踏著前人的足跡,用著前人留下的工具。
最後的最後這篇,還是在哈囉。
但這次不一樣,
我們有了自己建立的工具,
還多了看著這篇文章的你我 :-D