Hello: 真的哈罗

今天我想来点... Hello World!
又是快乐 Debug 的好日子呢!

昨天已经把程序跑起来了,
但又发现了很多奇怪的事情。
HEX Format 每个栏位到底代表什麽?
为什麽 Program Loader 跟 ELF 格式的开始位置对不起来?
真的要 * 16 吗?
今天就来尝试解开这些谜团吧!

可惜 HEX Format 的资料太零散,找到的原档连结也都失效,
只有第二手资讯真的不好整理,
找了很久 0203 格式的用法还不够确定,
欢迎熟悉的朋友帮忙补充。

HEX File Format

因为 Intel 官方和 Wikipedia 上面的运算都没写清楚,
我们参考 arm 的文件 HEX File Format
很明显地跟程序写的一样,每一行都分成 00、02、03、04、05等 Type。
档案中包含了程序开始位置、资料位置以及内容等资讯。

HEX File 单行格式:

  • 每一行::llaaaatt[dd...]cc
    • : 每一行 HEX 格式的第一个字元固定都是这个
    • ll 这一行的 [dd...] 有多少 byte
    • aaaa 代表接下来的资料从哪个 address 开始
    • tt 这一行後面的内容代表什麽
    • [dd...] 这一行所带的资料
    • cc 用来验证前面内容是否无误的 checksum

HEX File 单行资料的意义是由 tt 栏位决定:

  • 00 Data Record
    范例::10 0054 00 130101FD2326810213040103232EA4FC B2
    10 代表有 16 byte 的资料
    0054 资料位置从 0x0054 开始
    00 这行的资料为 Data Record
    130101FD2326810213040103232EA4FC 16 byte 资料
  • 01 End-of-File (EOF) Record
    范例::00000001FF
    内容固定,想知道细节可以参考上面提到的文件
  • 02 Extended Segment Address Record
    范例::02 0000 02 1000 EC
    02 代表有 2 byte 的资料
    0000 这边永远为 0
    02 这行的资料为 Extended Segment Address
    1000 8086 segment 是 16-byte alignment
  • 03 Start Segment Address Record
    范例::04 0000 03 100000B8 31
    04 代表有 4 byte 的资料
    0000 这边永远为 0
    03 这行的资料为 Start Segment Address Record
    1000 8086 Code segment (CS)暂存器数值,是 16-byte alignment
    00B8 8086 Instruction pointer(IP) 暂存器数值
  • 04 Extended Address Record
    范例::02 0000 04 00FF FB
    02 代表有 2 byte 的资料
    0000 这边永远为 0
    02 这行的资料为 Extended Address Record
    00FF 代表最高的两个 byte offset,在这边是 0x00FF0000
  • 05 Start Linear Address Record
    范例::04 0000 05 08000121 CD
    04 代表有 4 byte 的资料
    0000 这边永远为 0
    05 这行的资料为 Start Linear Address Record
    08000121 代表载入 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 低的位置?

恩....看起来还要再花时间研究一下,
不过夜深了,猴子也累了,今天就先写到这里吧!

大家最熟悉的Debug开发流程

hello world 写起来很简单,
但是执行的时候却碰到问题:
没有看到预期的的字元被一个一个写进指定的 address。

第一个步骤就是把 hello.o 反组译之後一条一条指令核对!
Function Call 的 JALJALR 看起来没什麽问题,
就来看 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


<<:  [第二十九只羊] 迷雾森林交响乐章 发牌逻辑

>>:  [Day29] 第二十九 - 补充技能交换前端以及与Express沟通api

【在厨房想30天的演算法】Day 13 资料结构:堆积 Heap

Aloha~又是我少女人妻 Uerica!今天是教师节啊~大家小时候都会写感谢恩师的卡片吗?记得刚上...

【Day 02】变数型态

前言 今天要来介绍一下 Python 中的各种变数型态,在程序中清楚了解自己要用的变数型态是非常重要...

Day#02 Swift 101

前言 就小女子浅见,现在iOS开发有几个选项: React Native Flutter Swift...

Day30|就这样持续下去吧!GO~

终於~让我熬到最後一天了! 虽然订阅的人数最後还是屈指可数 但看着镜子中的自己体态变好,心情真的也跟...

【左京淳的JAVA WEB学习笔记】第十六章 分页功能(查询用户购买纪录)

後台 管理员能在後台页面查询用户购买纪录及明细 第一次进入此页面时无参数,在表单填入以下资讯後返回结...