概述
? ?? ? 最近在拜讀《inux內核源碼剖析》一書,以下內容皆為此書引出,為后續的閱讀做個鋪墊。
? ?? ? 本篇講述如何搭建與使用qemu對內核啟動程序進行匯編級別調試,通過本篇可以了解如下內容:
- 使用qemu遠程調試方式跟蹤系統啟動過程
- gdb調試8086程序的方法
環境準備
- ubuntu16
- 安裝qemu與編譯鏈接工具
apt get install qemu bin86
啟動鏡像制作
我們要實現系統啟動后,在啟動界面上打印hello world!
as86 匯編代碼如下:
;
; display "hello world" in the boot screen
;
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
BOOTSEG = 0x07c0
entry start
start:
jmpi go, BOOTSEG
go:
mov ax, cs
mov ds, ax
mov es, ax
mov cx, #16 ;顯示16個字節
mov dx, #1804 ;
mov bx, #0x000c ;字符為紅色
mov bp, #msg1 ;字符串地址
mov ax, #0x1301 ;BIOS中斷調用, 功能0x13, 子功能01
int 0x10
loop0: jmp loop0
msg1: .byte 13, 10
.ascii "Hello World!"
.byte 13, 10
.org 510
.word 0xAA55
.text
endtext:
.data
enddata:
.bss
endbss:
makefile如下
all:
as86 -0 -a -o boot.o boot.s
ld86 -0 -s -o boot boot.o
dd bs=32 if=boot of=./boot_image skip=1
執行make即可生成鏡像文件boot_image.
開始調試
啟動qemu
用qemu啟動鏡像文件,命令如下:
qemu-system-i386 -s -S -vnc :2 boot_image
其中-s是設置gdbserver的監聽端口,-S則是讓cpu加電后被掛起。
-s
? ? ? ? Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234 (see gdb_usage).
-S
? ? ? ? Do not start CPU at startup (you must type ’c’ in the monitor).
啟動gdb
- 輸入
gdb -q
- 連接gdb server
target remote :1234
- 設置被調試環境架構
set architecture i8086
- 顯示將要執行的匯編指令
display /5i $cs * 0x10 + $pc
- 打斷點調試
? ?? ? qemu的 -S選項相當于將斷點打在CPU加電后要執行的第一條指令處,也就是BIOS程序的第一條指令。
? ?? ?通過info reg命令看到,cpu加電后的寄存器值,cs值為0xF000, pc值為0xFFF0。
? ?? ?不要直接單步程序,加電后bios代碼先運行,運行完后將系統引導程序加載到內存0x7c00開始的位置,之后跳轉到0x7c00處。
所以斷點應打在0x7c00處。輸入continue,程序停在斷點處。現在, 是不是看到了boot.s文件里的匯編指令?
注意事項
- 匯編代碼每行必須要以回車結尾
以回車結尾包括最后一行,否則as86會報錯(這個錯誤提示真讓人摸不著頭腦)
as: error reading input
- 關于加電后第一條指令地址
Intel手冊上寫了,上電后cpu加載的第一條指令的地址其實是0xFFFFFFF0,原文如下:
The address FFFFFFF0H is beyond the 1-MByte addressable range of the processor while in real-address mode. The
processor is initialized to this starting address as follows. The CS register has two parts: the visible segment
selector part and the hidden base address part. In real-address mode, the base address is normally formed by shifting the 16-bit segment selector value 4 bits to the left to produce a 20-bit base address. However, during a hardware reset, the segment selector in the CS register is loaded with F000H and the base address is loaded with FFFF0000H. The starting address is thus formed by adding the base address to the value in the EIP register (that
is, FFFF0000 + FFF0H = FFFFFFF0H).
只有在修改cs值后才會變成真正的實模式尋址
The first time the CS register is loaded with a new value after a hardware reset, the processor will follow the normal rule for address translation in real-address mode (that is, [CS base address = CS segment selector * 16]). To insure that the base address in the CS register remains unchanged until the EPROM based software-initialization code is completed, the code must not contain a far jump or far call or allow an interrupt to occur (which would cause the CS selector value to be changed).
總感覺還是沒說明白,應該還有很多故事,有興趣的可以挖一下。
參考文檔
- https://qemu.weilnetz.de/doc/qemu-doc.html
- 《linux內核完全剖析》
- Intel? 64 and IA-32 Architectures Software Developer’s Manual