一、实验内容
为了熟悉使用qemu和gdb进行的调试工作,我们进行如下的小练习:
(一)从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
(二)在初始化位置0x7c00设置实地址断点,测试断点正常。
(三)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
(四)自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
二、实验步骤
补充材料:
我们主要通过硬件模拟器qemu来进行各种实验。在实验的过程中我们可能会遇上各种各样的问题,调试是必要的。qemu支持使用gdb进行的强大而方便的调试。所以用好qemu和gdb是完成各种实验的基本要素
默认的gdb需要进行一些额外的配置才进行qemu的调试任务。qemu和gdb之间使用网络端口1234进行通讯。在打开qemu进行模拟之后,执行gdb并输
target remote :1234
即可连接qemu,此时qemu会进入停止状态,听从gdb的命令
另外,我们可能需要qemu在一开始便进入等待模式,则我们不再使用make qemu开始系统的运行,而使用make debug来完成这项工作。这样qemu便不会在gdb尚未连接的时候擅自运行了。
gdb的地址断点
在gdb命令行中,使用b *[地址]便可以在指定内存地址设置断点,当qemu中的cpu执行到指定地址时,便会将控制权交给gdb。
(一)从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行
1.修改gdbinit文件
首先,在 /moocos/ucore_lab/labcodes_answer/lab1_result/tools 目录下,修改gdbinit文件
进入目录:
cd ./moocos/ucore_lab/labcodes_answer/lab1_result/tools
修改方法为:
输入vim gdbinit
用D删除gdbinit中原有的内容(D为删除整行,x或X为删除单个字符)
将以下内容粘贴入gdbinit中
set architecture i8086
target remote :1234
2.make debug
输入cd ..,退回到./moocos/ucore_lab/labcodes_answer/lab1_result
输入make debug
随后执行make debug,
将弹出gdb窗口,如图所示:
在gdb窗口中使用si
命令即可单步追踪
(注意:你不必每次输入si,输入一次si后,只要按回车即可执行上次的指令)
在gdb界面下,可通过如下命令来看BIOS的代码
x /2i $pc(显示当前eip处的汇编指令)
(二)在初始化位置0x7c00设置实地址断点,测试断点正常。
1.修改gdbinit文件
进入目录:
cd ./moocos/ucore_lab/labcodes_answer/lab1_result/tools
修改方法与(一)相同,
修改的内容如下:
target remote :1234 //连接qemu,此时qemu会进入停止状态,听从gdb的命令
set architecture i8086 //设置当前调试的CPU是8086
b *0x7c00 //在0x7c00处设置断点。此地址是bootloader入口点地址,可看boot/bootasm.S的start地址处
c //continue简称,表示继续执行
x/10i $pc //显示当前eip处的汇编指令
2.make debug
输入cd ..,退回到./moocos/ucore_lab/labcodes_answer/lab1_result
输入make debug
(三)从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较
bootasm.S的完整代码为:
1 #include <asm.h>
2
3 # Start the CPU: switch to 32-bit protected mode, jump into C.
4 # The BIOS loads this code from the first sector of the hard disk into
5 # memory at physical address 0x7c00 and starts executing in real mode
6 # with %cs=0 %ip=7c00.
7
8 .set PROT_MODE_CSEG, 0x8 # kernel code segment selector
9 .set PROT_MODE_DSEG, 0x10 # kernel data segment selector
10 .set CR0_PE_ON, 0x1 # protected mode enable flag
11
12 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader
13 .globl start
14 start:
15 .code16 # Assemble for 16-bit mode
16 cli # Disable interrupts
17 cld # String operations increment
18
19 # Set up the important data segment registers (DS, ES, SS).
20 xorw %ax, %ax # Segment number zero
21 movw %ax, %ds # -> Data Segment
22 movw %ax, %es # -> Extra Segment
23 movw %ax, %ss # -> Stack Segment
24
25 # Enable A20:
26 # For backwards compatibility with the earliest PCs, physical
27 # address line 20 is tied low, so that addresses higher than
28 # 1MB wrap around to zero by default. This code undoes this.
29 seta20.1:
30 inb $0x64, %al # Wait for not busy(8042 input buffer empty).
31 testb $0x2, %al
32 jnz seta20.1
33
34 movb $0xd1, %al # 0xd1 -> port 0x64
35 outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
36
37 seta20.2:
38 inb $0x64, %al # Wait for not busy(8042 input buffer empty).
39 testb $0x2, %al
40 jnz seta20.2
41
42 movb $0xdf, %al # 0xdf -> port 0x60
43 outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
44
45 # Switch from real to protected mode, using a bootstrap GDT
46 # and segment translation that makes virtual addresses
47 # identical to physical addresses, so that the
48 # effective memory map does not change during the switch.
49 lgdt gdtdesc
50 movl %cr0, %eax
51 orl $CR0_PE_ON, %eax
52 movl %eax, %cr0
53
54 # Jump to next instruction, but in 32-bit code segment.
55 # Switches processor into 32-bit mode.
56 ljmp $PROT_MODE_CSEG, $protcseg
57
58 .code32 # Assemble for 32-bit mode
59 protcseg:
60 # Set up the protected-mode data segment registers
61 movw $PROT_MODE_DSEG, %ax # Our data segment selector
62 movw %ax, %ds # -> DS: Data Segment
63 movw %ax, %es # -> ES: Extra Segment
64 movw %ax, %fs # -> FS
65 movw %ax, %gs # -> GS
66 movw %ax, %ss # -> SS: Stack Segment
67
68 # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
69 movl $0x0, %ebp
70 movl $start, %esp
71 call bootmain
72
73 # If bootmain returns (it shouldn't), loop.
74 spin:
75 jmp spin
76
77 # Bootstrap GDT
78 .p2align 2 # force 4 byte alignment
79 gdt:
80 SEG_NULLASM # null seg
81 SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
82 SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
83
84 gdtdesc:
85 .word 0x17 # sizeof(gdt) - 1
86 .long gdt # address gdt
bootblock.asm的完整代码为:
1 obj/bootblock.o: file format elf32-i386
2
3
4 Disassembly of section .text:
5
6 00007c00 <start>:
7
8 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader
9 .globl start
10 start:
11 .code16 # Assemble for 16-bit mode
12 cli # Disable interrupts
13 7c00: fa cli
14 cld # String operations increment
15 7c01: fc cld
16
17 # Set up the important data segment registers (DS, ES, SS).
18 xorw %ax, %ax # Segment number zero
19 7c02: 31 c0 xor %eax,%eax
20 movw %ax, %ds # -> Data Segment
21 7c04: 8e d8 mov %eax,%ds
22 movw %ax, %es # -> Extra Segment
23 7c06: 8e c0 mov %eax,%es
24 movw %ax, %ss # -> Stack Segment
25 7c08: 8e d0 mov %eax,%ss
26
27 00007c0a <seta20.1>:
28 # Enable A20:
29 # For backwards compatibility with the earliest PCs, physical
30 # address line 20 is tied low, so that addresses higher than
31 # 1MB wrap around to zero by default. This code undoes this.
32 seta20.1:
33 inb $0x64, %al # Wait for not busy(8042 input buffer empty).
34 7c0a: e4 64 in $0x64,%al
35 testb $0x2, %al
36 7c0c: a8 02 test $0x2,%al
37 jnz seta20.1
38 7c0e: 75 fa jne 7c0a <seta20.1>
39
40 movb $0xd1, %al # 0xd1 -> port 0x64
41 7c10: b0 d1 mov $0xd1,%al
42 outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
43 7c12: e6 64 out %al,$0x64
44
45 00007c14 <seta20.2>:
46
47 seta20.2:
48 inb $0x64, %al # Wait for not busy(8042 input buffer empty).
49 7c14: e4 64 in $0x64,%al
50 testb $0x2, %al
51 7c16: a8 02 test $0x2,%al
52 jnz seta20.2
53 7c18: 75 fa jne 7c14 <seta20.2>
54
55 movb $0xdf, %al # 0xdf -> port 0x60
56 7c1a: b0 df mov $0xdf,%al
57 outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
58 7c1c: e6 60 out %al,$0x60
59
60 # Switch from real to protected mode, using a bootstrap GDT
61 # and segment translation that makes virtual addresses
62 # identical to physical addresses, so that the
63 # effective memory map does not change during the switch.
64 lgdt gdtdesc
65 7c1e: 0f 01 16 lgdtl (%esi)
66 7c21: 6c insb (%dx),%es:(%edi)
67 7c22: 7c 0f jl 7c33 <protcseg+0x1>
68 movl %cr0, %eax
69 7c24: 20 c0 and %al,%al
70 orl $CR0_PE_ON, %eax
71 7c26: 66 83 c8 01 or $0x1,%ax
72 movl %eax, %cr0
73 7c2a: 0f 22 c0 mov %eax,%cr0
74
75 # Jump to next instruction, but in 32-bit code segment.
76 # Switches processor into 32-bit mode.
77 ljmp $PROT_MODE_CSEG, $protcseg
78 7c2d: ea 32 7c 08 00 66 b8 ljmp $0xb866,$0x87c32
79
80 00007c32 <protcseg>:
81
82 .code32 # Assemble for 32-bit mode
83 protcseg:
84 # Set up the protected-mode data segment registers
85 movw $PROT_MODE_DSEG, %ax # Our data segment selector
86 7c32: 66 b8 10 00 mov $0x10,%ax
87 movw %ax, %ds # -> DS: Data Segment
88 7c36: 8e d8 mov %eax,%ds
89 movw %ax, %es # -> ES: Extra Segment
90 7c38: 8e c0 mov %eax,%es
91 movw %ax, %fs # -> FS
92 7c3a: 8e e0 mov %eax,%fs
93 movw %ax, %gs # -> GS
94 7c3c: 8e e8 mov %eax,%gs
95 movw %ax, %ss # -> SS: Stack Segment
96 7c3e: 8e d0 mov %eax,%ss
97
98 # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
99 movl $0x0, %ebp
100 7c40: bd 00 00 00 00 mov $0x0,%ebp
101 movl $start, %esp
102 7c45: bc 00 7c 00 00 mov $0x7c00,%esp
103 call bootmain
104 7c4a: e8 b1 00 00 00 call 7d00 <bootmain>
105
106 00007c4f <spin>:
107
108 # If bootmain returns (it shouldn't), loop.
109 spin:
110 jmp spin
111 7c4f: eb fe jmp 7c4f <spin>
112 7c51: 8d 76 00 lea 0x0(%esi),%esi
113
114 00007c54 <gdt>:
115 ...
116 7c5c: ff (bad)
117 7c5d: ff 00 incl (%eax)
118 7c5f: 00 00 add %al,(%eax)
119 7c61: 9a cf 00 ff ff 00 00 lcall $0x0,$0xffff00cf
120 7c68: 00 92 cf 00 17 00 add %dl,0x1700cf(%edx)
121
122 00007c6c <gdtdesc>:
123 7c6c: 17 pop %ss
124 7c6d: 00 54 7c 00 add %dl,0x0(%esp,%edi,2)
125 ...
126
127 00007c72 <readseg>:
128 /* *
129 * readseg - read @count bytes at @offset from kernel into virtual address @va,
130 * might copy more than asked.
131 * */
132 static void
133 readseg(uintptr_t va, uint32_t count, uint32_t offset) {
134 7c72: 55 push %ebp
135 7c73: 89 e5 mov %esp,%ebp
136 7c75: 57 push %edi
137 7c76: 56 push %esi
138 7c77: 89 c6 mov %eax,%esi
139 7c79: 53 push %ebx
140 uintptr_t end_va = va + count;
141 7c7a: 8d 04 10 lea (%eax,%edx,1),%eax
142
143 // round down to sector boundary
144 va -= offset % SECTSIZE;
145 7c7d: 31 d2 xor %edx,%edx
146 /* *
147 * readseg - read @count bytes at @offset from kernel into virtual address @va,
148 * might copy more than asked.
149 * */
150 static void
151 readseg(uintptr_t va, uint32_t count, uint32_t offset) {
152 7c7f: 53 push %ebx
153 uintptr_t end_va = va + count;
154 7c80: 89 45 f0 mov %eax,-0x10(%ebp)
155
156 // round down to sector boundary
157 va -= offset % SECTSIZE;
158 7c83: 89 c8 mov %ecx,%eax
159 7c85: f7 35 e4 7d 00 00 divl 0x7de4
160 7c8b: 29 d6 sub %edx,%esi
161
162 // translate from bytes to sectors; kernel starts at sector 1
163 uint32_t secno = (offset / SECTSIZE) + 1;
164 7c8d: 8d 58 01 lea 0x1(%eax),%ebx
165
166 // If this is too slow, we could read lots of sectors at a time.
167 // We'd write more to memory than asked, but it doesn't matter --
168 // we load in increasing order.
169 for (; va < end_va; va += SECTSIZE, secno ++) {
170 7c90: 3b 75 f0 cmp -0x10(%ebp),%esi
171 7c93: 73 65 jae 7cfa <readseg+0x88>
172 static inline void ltr(uint16_t sel) __attribute__((always_inline));
173
174 static inline uint8_t
175 inb(uint16_t port) {
176 uint8_t data;
177 asm volatile ("inb %1, %0" : "=a" (data) : "d" (port));
178 7c95: ba f7 01 00 00 mov $0x1f7,%edx
179 7c9a: ec in (%dx),%al
180 struct elfhdr * ELFHDR = ((struct elfhdr *)0x10000) ; // scratch space
181
182 /* waitdisk - wait for disk ready */
183 static void
184 waitdisk(void) {
185 while ((inb(0x1F7) & 0xC0) != 0x40)
186 7c9b: 83 e0 c0 and $0xffffffc0,%eax
187 7c9e: 3c 40 cmp $0x40,%al
188 7ca0: 75 f3 jne 7c95 <readseg+0x23>
189 : "memory", "cc");
190 }
191
192 static inline void
193 outb(uint16_t port, uint8_t data) {
194 asm volatile ("outb %0, %1" :: "a" (data), "d" (port));
195 7ca2: b2 f2 mov $0xf2,%dl
196 7ca4: b0 01 mov $0x1,%al
197 7ca6: ee out %al,(%dx)
198 7ca7: 0f b6 c3 movzbl %bl,%eax
199 7caa: b2 f3 mov $0xf3,%dl
200 7cac: ee out %al,(%dx)
201 7cad: 0f b6 c7 movzbl %bh,%eax
202 7cb0: b2 f4 mov $0xf4,%dl
203 7cb2: ee out %al,(%dx)
204 waitdisk();
205
206 outb(0x1F2, 1); // count = 1
207 outb(0x1F3, secno & 0xFF);
208 outb(0x1F4, (secno >> 8) & 0xFF);
209 outb(0x1F5, (secno >> 16) & 0xFF);
210 7cb3: 89 d8 mov %ebx,%eax
211 7cb5: b2 f5 mov $0xf5,%dl
212 7cb7: c1 e8 10 shr $0x10,%eax
213 7cba: 0f b6 c0 movzbl %al,%eax
214 7cbd: ee out %al,(%dx)
215 outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
216 7cbe: 89 d8 mov %ebx,%eax
217 7cc0: b2 f6 mov $0xf6,%dl
218 7cc2: c1 e8 18 shr $0x18,%eax
219 7cc5: 83 e0 0f and $0xf,%eax
220 7cc8: 83 c8 e0 or $0xffffffe0,%eax
221 7ccb: ee out %al,(%dx)
222 7ccc: b0 20 mov $0x20,%al
223 7cce: b2 f7 mov $0xf7,%dl
224 7cd0: ee out %al,(%dx)
225 static inline void ltr(uint16_t sel) __attribute__((always_inline));
226
227 static inline uint8_t
228 inb(uint16_t port) {
229 uint8_t data;
230 asm volatile ("inb %1, %0" : "=a" (data) : "d" (port));
231 7cd1: ba f7 01 00 00 mov $0x1f7,%edx
232 7cd6: ec in (%dx),%al
233 struct elfhdr * ELFHDR = ((struct elfhdr *)0x10000) ; // scratch space
234
235 /* waitdisk - wait for disk ready */
236 static void
237 waitdisk(void) {
238 while ((inb(0x1F7) & 0xC0) != 0x40)
239 7cd7: 83 e0 c0 and $0xffffffc0,%eax
240 7cda: 3c 40 cmp $0x40,%al
241 7cdc: 75 f3 jne 7cd1 <readseg+0x5f>
242
243 // wait for disk to be ready
244 waitdisk();
245
246 // read a sector
247 insl(0x1F0, dst, SECTSIZE / 4);
248 7cde: 8b 0d e4 7d 00 00 mov 0x7de4,%ecx
249 return data;
250 }
251
252 static inline void
253 insl(uint32_t port, void *addr, int cnt) {
254 asm volatile (
255 7ce4: 89 f7 mov %esi,%edi
256 7ce6: ba f0 01 00 00 mov $0x1f0,%edx
257 7ceb: c1 e9 02 shr $0x2,%ecx
258 7cee: fc cld
259 7cef: f2 6d repnz insl (%dx),%es:(%edi)
260 uint32_t secno = (offset / SECTSIZE) + 1;
261
262 // If this is too slow, we could read lots of sectors at a time.
263 // We'd write more to memory than asked, but it doesn't matter --
264 // we load in increasing order.
265 for (; va < end_va; va += SECTSIZE, secno ++) {
266 7cf1: 03 35 e4 7d 00 00 add 0x7de4,%esi
267 7cf7: 43 inc %ebx
268 7cf8: eb 96 jmp 7c90 <readseg+0x1e>
269 readsect((void *)va, secno);
270 }
271 }
272 7cfa: 58 pop %eax
273 7cfb: 5b pop %ebx
274 7cfc: 5e pop %esi
275 7cfd: 5f pop %edi
276 7cfe: 5d pop %ebp
277 7cff: c3 ret
278
279 00007d00 <bootmain>:
280
281 /* bootmain - the entry of bootloader */
282 void
283 bootmain(void) {
284 // read the 1st page off disk
285 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
286 7d00: a1 e4 7d 00 00 mov 0x7de4,%eax
287 7d05: 31 c9 xor %ecx,%ecx
288 }
289 }
290
291 /* bootmain - the entry of bootloader */
292 void
293 bootmain(void) {
294 7d07: 55 push %ebp
295 7d08: 89 e5 mov %esp,%ebp
296 7d0a: 56 push %esi
297 // read the 1st page off disk
298 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
299 7d0b: 8d 14 c5 00 00 00 00 lea 0x0(,%eax,8),%edx
300 7d12: a1 e0 7d 00 00 mov 0x7de0,%eax
301 }
302 }
303
304 /* bootmain - the entry of bootloader */
305 void
306 bootmain(void) {
307 7d17: 53 push %ebx
308 // read the 1st page off disk
309 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
310 7d18: e8 55 ff ff ff call 7c72 <readseg>
311
312 // is this a valid ELF?
313 if (ELFHDR->e_magic != ELF_MAGIC) {
314 7d1d: a1 e0 7d 00 00 mov 0x7de0,%eax
315 7d22: 81 38 7f 45 4c 46 cmpl $0x464c457f,(%eax)
316 7d28: 75 3a jne 7d64 <bootmain+0x64>
317 }
318
319 struct proghdr *ph, *eph;
320
321 // load each program segment (ignores ph flags)
322 ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
323 7d2a: 8b 58 1c mov 0x1c(%eax),%ebx
324 7d2d: 01 c3 add %eax,%ebx
325 eph = ph + ELFHDR->e_phnum;
326 7d2f: 0f b7 40 2c movzwl 0x2c(%eax),%eax
327 7d33: c1 e0 05 shl $0x5,%eax
328 7d36: 8d 34 03 lea (%ebx,%eax,1),%esi
329 for (; ph < eph; ph ++) {
330 7d39: 39 f3 cmp %esi,%ebx
331 7d3b: 73 18 jae 7d55 <bootmain+0x55>
332 readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
333 7d3d: 8b 43 08 mov 0x8(%ebx),%eax
334 struct proghdr *ph, *eph;
335
336 // load each program segment (ignores ph flags)
337 ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
338 eph = ph + ELFHDR->e_phnum;
339 for (; ph < eph; ph ++) {
340 7d40: 83 c3 20 add $0x20,%ebx
341 readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
342 7d43: 8b 4b e4 mov -0x1c(%ebx),%ecx
343 7d46: 8b 53 f4 mov -0xc(%ebx),%edx
344 7d49: 25 ff ff ff 00 and $0xffffff,%eax
345 7d4e: e8 1f ff ff ff call 7c72 <readseg>
346 7d53: eb e4 jmp 7d39 <bootmain+0x39>
347 }
348
349 // call the entry point from the ELF header
350 // note: does not return
351 ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
352 7d55: a1 e0 7d 00 00 mov 0x7de0,%eax
353 7d5a: 8b 40 18 mov 0x18(%eax),%eax
354 7d5d: 25 ff ff ff 00 and $0xffffff,%eax
355 7d62: ff d0 call *%eax
356 asm volatile ("outb %0, %1" :: "a" (data), "d" (port));
357 }
358
359 static inline void
360 outw(uint16_t port, uint16_t data) {
361 asm volatile ("outw %0, %1" :: "a" (data), "d" (port));
362 7d64: b8 00 8a ff ff mov $0xffff8a00,%eax
363 7d69: 89 c2 mov %eax,%edx
364 7d6b: 66 ef out %ax,(%dx)
365 7d6d: b8 00 8e ff ff mov $0xffff8e00,%eax
366 7d72: 66 ef out %ax,(%dx)
367 7d74: eb fe jmp 7d74 <bootmain+0x74>
反汇编得到的代码是:
下图是bootasm.S中14到28行的代码:
下面是bootblock.asm中10到25行的代码
比较可知,三者基本一致。
(四)自己找一个bootloader或内核中的代码位置,设置断点并进行测试
略