将常量字符串值传递给寄存器
Passing Constant String Value to Register
我正在为 xv6 OS 重写引导扇区作为一项任务,并试图执行一个无法正确输出到 QEMU 的简单框架。
这是使用 QEMU 模拟器(系统 i386)和 Linux 子系统 Windows(使用 Ubuntu 18.04.1 LTS)。系统在向%al
传递文字时确实正确显示了一个字符,并进入了后续的死循环。
.code16
.globl start
start:
cli
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movb [=10=]x0e, %ah
movb hello, %al
movb [=10=], %bh
movb , %bl
int [=10=]x10
stop:
jmp stop
hello:
.string "Hello world."
.org 0x1fe
.word 0xAA55
我希望得到 H
的输出,但打印出来的只是 S
;在大多数其他情况下,它根本不输出任何内容,除非使用文字。
编辑:这是构建后使用 objdump
对二进制文件的反汇编:
bootsector.img: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <start>:
0: fa cli
1: 31 c0 xor %eax,%eax
3: 8e d8 mov %eax,%ds
5: 8e c0 mov %eax,%es
7: 8e d0 mov %eax,%ss
9: b4 0e mov [=11=]xe,%ah
b: a0 00 00 b7 00 b3 03 movabs 0x10cd03b300b70000,%al
12: cd 10
0000000000000014 <stop>:
14: eb fe jmp 14 <stop>
0000000000000016 <hello>:
16: 48 rex.W
17: 65 6c gs insb (%dx),%es:(%rdi)
19: 6c insb (%dx),%es:(%rdi)
1a: 6f outsl %ds:(%rsi),(%dx)
1b: 20 77 6f and %dh,0x6f(%rdi)
1e: 72 6c jb 8c <hello+0x76>
20: 64 2e 00 00 fs add %al,%cs:(%rax)
...
1fc: 00 00 add %al,(%rax)
1fe: 55 push %rbp
1ff: aa stos %al,%es:(%rdi)
我用来构建和执行代码的步骤:
$ as bootsector.S -o bootsector.img
$ objcopy -O binary bootsector.img
$ qemu-system-i386 bootsector.img -curses
问题是您正在将代码组装到一个目标文件中,并且您正在将该对象直接转换为二进制文件。您需要添加一个 linking 进程以从 ELF 对象生成可执行文件并将其转换为二进制文件。 linking 步骤还指定了 0x7c00 的原点。
您需要做的是:
- Assemble 带有 AS (GNU assembler) 的引导扇区到 ELF 对象。我建议输出为 ELF32 而不是 ELF64。
- 使用 LD(GNU 链接器)将 ELF 对象link 直接转换为 ELF 可执行文件或二进制文件。使用 LD 指定原点 0x7c00.
- 将引导扇区放在映像文件中。
您可以使用的命令是:
as boot.s -o boot.o
ld -Ttext=0x7c00 --oformat=binary boot.o -o boot.bin
boot.bin
应该是最终的二进制文件并且应该正好是 512 字节。您通常会将引导扇区放在磁盘映像中,但出于测试目的,您应该能够 运行 直接使用 QEMU 将其设置为:
qemu-system-i386 -fda boot.bin
如果使用 16 位指令解码从原点 0x7c00 开始使用命令转储二进制文件:
ndisasm -b16 -o 0x7c00 boot.bin
您应该得到类似于以下内容的输出:
00007C00 FA cli
00007C01 31C0 xor ax,ax
00007C03 8ED8 mov ds,ax
00007C05 8EC0 mov es,ax
00007C07 8ED0 mov ss,ax
00007C09 B40E mov ah,0xe
00007C0B A0167C mov al,[0x7c16]
00007C0E B700 mov bh,0x0
00007C10 B307 mov bl,0x7
00007C12 CD10 int 0x10
00007C14 EBFE jmp short 0x7c14
00007C16 48 dec ax
00007C17 656C gs insb
00007C19 6C insb
00007C1A 6F outsw
00007C1B 20776F and [bx+0x6f],dh
00007C1E 726C jc 0x7c8c
00007C20 642E0000 add [cs:bx+si],al
00007C24 0000 add [bx+si],al
00007C26 0000 add [bx+si],al
00007C28 0000 add [bx+si],al
[snip for brevity]
00007DFA 0000 add [bx+si],al
00007DFC 0000 add [bx+si],al
00007DFE 55 push bp
00007DFF AA stosb
另一种构建方式
也可以用AS assemble 到目标文件,用LD link 到ELF,然后用OBJCOPY 将ELF 可执行文件转换成二进制文件。这也允许您使用 ELF 可执行文件通过远程 GDB 等进行符号调试。您还可以使用 OBJDUMP 而不是 NDISASM 来查看生成的代码。
命令顺序为:
as boot.s -o boot.o
ld -Ttext=0x7c00 boot.o -o boot.elf
objcopy -O binary boot.elf boot.bin
现在您可以使用 OBJDUMP 转储 boot.elf
。请注意,您需要指定要解码为 16 位代码。 OBJDUMP 命令将是:
objdump -Dx -Mi8086 boot.elf
输出将类似于此(如果您使用的是 64 位工具链):
boot.elf: file format elf64-x86-64
boot.elf
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000007c00
Program Header:
LOAD off 0x0000000000000000 vaddr 0x0000000000007000 paddr 0x0000000000007000 align 2**12
filesz 0x0000000000000e00 memsz 0x0000000000000e00 flags r-x
LOAD off 0x00000000000010e8 vaddr 0x00000000004000e8 paddr 0x00000000004000e8 align 2**12
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
NOTE off 0x00000000000010e8 vaddr 0x00000000004000e8 paddr 0x00000000004000e8 align 2**3
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
Sections:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.property 00000020 00000000004000e8 00000000004000e8 000010e8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .text 00000200 0000000000007c00 0000000000007c00 00000c00 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
00000000004000e8 l d .note.gnu.property 0000000000000000 .note.gnu.property
0000000000007c00 l d .text 0000000000000000 .text
0000000000000000 l df *ABS* 0000000000000000 boot.o
0000000000007c16 l .text 0000000000000000 hello
0000000000007c14 l .text 0000000000000000 stop
0000000000007c00 g .text 0000000000000000 _start
0000000000008000 g .text 0000000000000000 __bss_start
0000000000008000 g .text 0000000000000000 _edata
0000000000008000 g .text 0000000000000000 _end
Disassembly of section .note.gnu.property:
00000000004000e8 <.note.gnu.property>:
4000e8: 04 00 add [=16=]x0,%al
4000ea: 00 00 add %al,(%rax)
4000ec: 10 00 adc %al,(%rax)
4000ee: 00 00 add %al,(%rax)
4000f0: 05 00 00 00 47 add [=16=]x47000000,%eax
4000f5: 4e 55 rex.WRX push %rbp
4000f7: 00 01 add %al,(%rcx)
4000f9: 00 00 add %al,(%rax)
4000fb: c0 04 00 00 rolb [=16=]x0,(%rax,%rax,1)
4000ff: 00 01 add %al,(%rcx)
400101: 00 00 add %al,(%rax)
400103: 00 00 add %al,(%rax)
400105: 00 00 add %al,(%rax)
...
Disassembly of section .text:
0000000000007c00 <_start>:
7c00: fa cli
7c01: 31 c0 xor %eax,%eax
7c03: 8e d8 mov %eax,%ds
7c05: 8e c0 mov %eax,%es
7c07: 8e d0 mov %eax,%ss
7c09: b4 0e mov [=16=]xe,%ah
7c0b: a0 16 7c b7 00 b3 07 movabs 0x10cd07b300b77c16,%al
7c12: cd 10
0000000000007c14 <stop>:
7c14: eb fe jmp 7c14 <stop>
0000000000007c16 <hello>:
7c16: 48 rex.W
7c17: 65 6c gs insb (%dx),%es:(%rdi)
7c19: 6c insb (%dx),%es:(%rdi)
7c1a: 6f outsl %ds:(%rsi),(%dx)
7c1b: 20 77 6f and %dh,0x6f(%rdi)
7c1e: 72 6c jb 7c8c <hello+0x76>
7c20: 64 2e 00 00 fs add %al,%cs:(%rax)
...
7dfc: 00 00 add %al,(%rax)
7dfe: 55 push %rbp
7dff: aa stos %al,%es:(%rdi)
观察结果
您可能想知道为什么在输出中出现奇怪的 movabs
指令:
bootsector.img: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <start>:
0: fa cli
1: 31 c0 xor %eax,%eax
3: 8e d8 mov %eax,%ds
5: 8e c0 mov %eax,%es
7: 8e d0 mov %eax,%ss
9: b4 0e mov [=17=]xe,%ah
b: a0 00 00 b7 00 b3 03 movabs 0x10cd03b300b70000,%al
12: cd 10
OBJDUMP 不知道代码是 16 位的(该信息不保留在 ELF 对象文件中)。 OBJDUMP 默认为 64 位解码,因为目标文件格式为 elf64-x86-64
(ELF64)。默认情况下,64 位工具链上的 AS 生成 64 位对象。 OBJDUMP 将默认为 ELF64 文件的 64 位解码,这就是导致解码不正确的原因。您可以使用 -Mi8086
请求使用 OBJDUMP 进行 16 位解码。
其他推荐
考虑将 start
重命名为 _start
以防止 linker 发出无法找到入口点的警告。该警告不是致命的,可以忽略。另一种方法是通过添加额外选项 --entry=start
来告诉 LD 入口点是 start
。该命令可能如下所示:
ld -Ttext=0x7c00 --entry=start boot.o -o boot.elf
我正在为 xv6 OS 重写引导扇区作为一项任务,并试图执行一个无法正确输出到 QEMU 的简单框架。
这是使用 QEMU 模拟器(系统 i386)和 Linux 子系统 Windows(使用 Ubuntu 18.04.1 LTS)。系统在向%al
传递文字时确实正确显示了一个字符,并进入了后续的死循环。
.code16
.globl start
start:
cli
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movb [=10=]x0e, %ah
movb hello, %al
movb [=10=], %bh
movb , %bl
int [=10=]x10
stop:
jmp stop
hello:
.string "Hello world."
.org 0x1fe
.word 0xAA55
我希望得到 H
的输出,但打印出来的只是 S
;在大多数其他情况下,它根本不输出任何内容,除非使用文字。
编辑:这是构建后使用 objdump
对二进制文件的反汇编:
bootsector.img: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <start>:
0: fa cli
1: 31 c0 xor %eax,%eax
3: 8e d8 mov %eax,%ds
5: 8e c0 mov %eax,%es
7: 8e d0 mov %eax,%ss
9: b4 0e mov [=11=]xe,%ah
b: a0 00 00 b7 00 b3 03 movabs 0x10cd03b300b70000,%al
12: cd 10
0000000000000014 <stop>:
14: eb fe jmp 14 <stop>
0000000000000016 <hello>:
16: 48 rex.W
17: 65 6c gs insb (%dx),%es:(%rdi)
19: 6c insb (%dx),%es:(%rdi)
1a: 6f outsl %ds:(%rsi),(%dx)
1b: 20 77 6f and %dh,0x6f(%rdi)
1e: 72 6c jb 8c <hello+0x76>
20: 64 2e 00 00 fs add %al,%cs:(%rax)
...
1fc: 00 00 add %al,(%rax)
1fe: 55 push %rbp
1ff: aa stos %al,%es:(%rdi)
我用来构建和执行代码的步骤:
$ as bootsector.S -o bootsector.img
$ objcopy -O binary bootsector.img
$ qemu-system-i386 bootsector.img -curses
问题是您正在将代码组装到一个目标文件中,并且您正在将该对象直接转换为二进制文件。您需要添加一个 linking 进程以从 ELF 对象生成可执行文件并将其转换为二进制文件。 linking 步骤还指定了 0x7c00 的原点。
您需要做的是:
- Assemble 带有 AS (GNU assembler) 的引导扇区到 ELF 对象。我建议输出为 ELF32 而不是 ELF64。
- 使用 LD(GNU 链接器)将 ELF 对象link 直接转换为 ELF 可执行文件或二进制文件。使用 LD 指定原点 0x7c00.
- 将引导扇区放在映像文件中。
您可以使用的命令是:
as boot.s -o boot.o
ld -Ttext=0x7c00 --oformat=binary boot.o -o boot.bin
boot.bin
应该是最终的二进制文件并且应该正好是 512 字节。您通常会将引导扇区放在磁盘映像中,但出于测试目的,您应该能够 运行 直接使用 QEMU 将其设置为:
qemu-system-i386 -fda boot.bin
如果使用 16 位指令解码从原点 0x7c00 开始使用命令转储二进制文件:
ndisasm -b16 -o 0x7c00 boot.bin
您应该得到类似于以下内容的输出:
00007C00 FA cli 00007C01 31C0 xor ax,ax 00007C03 8ED8 mov ds,ax 00007C05 8EC0 mov es,ax 00007C07 8ED0 mov ss,ax 00007C09 B40E mov ah,0xe 00007C0B A0167C mov al,[0x7c16] 00007C0E B700 mov bh,0x0 00007C10 B307 mov bl,0x7 00007C12 CD10 int 0x10 00007C14 EBFE jmp short 0x7c14 00007C16 48 dec ax 00007C17 656C gs insb 00007C19 6C insb 00007C1A 6F outsw 00007C1B 20776F and [bx+0x6f],dh 00007C1E 726C jc 0x7c8c 00007C20 642E0000 add [cs:bx+si],al 00007C24 0000 add [bx+si],al 00007C26 0000 add [bx+si],al 00007C28 0000 add [bx+si],al [snip for brevity] 00007DFA 0000 add [bx+si],al 00007DFC 0000 add [bx+si],al 00007DFE 55 push bp 00007DFF AA stosb
另一种构建方式
也可以用AS assemble 到目标文件,用LD link 到ELF,然后用OBJCOPY 将ELF 可执行文件转换成二进制文件。这也允许您使用 ELF 可执行文件通过远程 GDB 等进行符号调试。您还可以使用 OBJDUMP 而不是 NDISASM 来查看生成的代码。
命令顺序为:
as boot.s -o boot.o
ld -Ttext=0x7c00 boot.o -o boot.elf
objcopy -O binary boot.elf boot.bin
现在您可以使用 OBJDUMP 转储 boot.elf
。请注意,您需要指定要解码为 16 位代码。 OBJDUMP 命令将是:
objdump -Dx -Mi8086 boot.elf
输出将类似于此(如果您使用的是 64 位工具链):
boot.elf: file format elf64-x86-64 boot.elf architecture: i386:x86-64, flags 0x00000112: EXEC_P, HAS_SYMS, D_PAGED start address 0x0000000000007c00 Program Header: LOAD off 0x0000000000000000 vaddr 0x0000000000007000 paddr 0x0000000000007000 align 2**12 filesz 0x0000000000000e00 memsz 0x0000000000000e00 flags r-x LOAD off 0x00000000000010e8 vaddr 0x00000000004000e8 paddr 0x00000000004000e8 align 2**12 filesz 0x0000000000000020 memsz 0x0000000000000020 flags r-- NOTE off 0x00000000000010e8 vaddr 0x00000000004000e8 paddr 0x00000000004000e8 align 2**3 filesz 0x0000000000000020 memsz 0x0000000000000020 flags r-- Sections: Idx Name Size VMA LMA File off Algn 0 .note.gnu.property 00000020 00000000004000e8 00000000004000e8 000010e8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 00000200 0000000000007c00 0000000000007c00 00000c00 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE SYMBOL TABLE: 00000000004000e8 l d .note.gnu.property 0000000000000000 .note.gnu.property 0000000000007c00 l d .text 0000000000000000 .text 0000000000000000 l df *ABS* 0000000000000000 boot.o 0000000000007c16 l .text 0000000000000000 hello 0000000000007c14 l .text 0000000000000000 stop 0000000000007c00 g .text 0000000000000000 _start 0000000000008000 g .text 0000000000000000 __bss_start 0000000000008000 g .text 0000000000000000 _edata 0000000000008000 g .text 0000000000000000 _end Disassembly of section .note.gnu.property: 00000000004000e8 <.note.gnu.property>: 4000e8: 04 00 add [=16=]x0,%al 4000ea: 00 00 add %al,(%rax) 4000ec: 10 00 adc %al,(%rax) 4000ee: 00 00 add %al,(%rax) 4000f0: 05 00 00 00 47 add [=16=]x47000000,%eax 4000f5: 4e 55 rex.WRX push %rbp 4000f7: 00 01 add %al,(%rcx) 4000f9: 00 00 add %al,(%rax) 4000fb: c0 04 00 00 rolb [=16=]x0,(%rax,%rax,1) 4000ff: 00 01 add %al,(%rcx) 400101: 00 00 add %al,(%rax) 400103: 00 00 add %al,(%rax) 400105: 00 00 add %al,(%rax) ... Disassembly of section .text: 0000000000007c00 <_start>: 7c00: fa cli 7c01: 31 c0 xor %eax,%eax 7c03: 8e d8 mov %eax,%ds 7c05: 8e c0 mov %eax,%es 7c07: 8e d0 mov %eax,%ss 7c09: b4 0e mov [=16=]xe,%ah 7c0b: a0 16 7c b7 00 b3 07 movabs 0x10cd07b300b77c16,%al 7c12: cd 10 0000000000007c14 <stop>: 7c14: eb fe jmp 7c14 <stop> 0000000000007c16 <hello>: 7c16: 48 rex.W 7c17: 65 6c gs insb (%dx),%es:(%rdi) 7c19: 6c insb (%dx),%es:(%rdi) 7c1a: 6f outsl %ds:(%rsi),(%dx) 7c1b: 20 77 6f and %dh,0x6f(%rdi) 7c1e: 72 6c jb 7c8c <hello+0x76> 7c20: 64 2e 00 00 fs add %al,%cs:(%rax) ... 7dfc: 00 00 add %al,(%rax) 7dfe: 55 push %rbp 7dff: aa stos %al,%es:(%rdi)
观察结果
您可能想知道为什么在输出中出现奇怪的 movabs
指令:
bootsector.img: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <start>: 0: fa cli 1: 31 c0 xor %eax,%eax 3: 8e d8 mov %eax,%ds 5: 8e c0 mov %eax,%es 7: 8e d0 mov %eax,%ss 9: b4 0e mov [=17=]xe,%ah b: a0 00 00 b7 00 b3 03 movabs 0x10cd03b300b70000,%al 12: cd 10
OBJDUMP 不知道代码是 16 位的(该信息不保留在 ELF 对象文件中)。 OBJDUMP 默认为 64 位解码,因为目标文件格式为 elf64-x86-64
(ELF64)。默认情况下,64 位工具链上的 AS 生成 64 位对象。 OBJDUMP 将默认为 ELF64 文件的 64 位解码,这就是导致解码不正确的原因。您可以使用 -Mi8086
请求使用 OBJDUMP 进行 16 位解码。
其他推荐
考虑将
start
重命名为_start
以防止 linker 发出无法找到入口点的警告。该警告不是致命的,可以忽略。另一种方法是通过添加额外选项--entry=start
来告诉 LD 入口点是start
。该命令可能如下所示:ld -Ttext=0x7c00 --entry=start boot.o -o boot.elf