assembly 在QEMU中加载和运行带有32位代码的原始二进制文件

rqcrx0a6  于 2023-08-06  发布在  其他
关注(0)|答案(1)|浏览(135)

我有一个程序集(NASM)文件:

  1. bits 32
  2. start:
  3. mov dword [0xb8000], 0x2f4b2f4f
  4. hlt

字符串
这将生成一个二进制文件,其中包含:

  1. C7 05 00 80 0B 00 4F 2F 4B 2F F4


有没有一种方法可以在QEMU中执行这个二进制文件中的代码,而不需要更改代码,也不需要添加头文件(比如Multiboot或类似的)?我希望保持二进制文件的原样。

fdbelqdn

fdbelqdn1#

我不知道为什么你不想在你的代码中使用ELF可执行文件,特别是如果你在写内核的话。
没有办法让QEMU直接运行任意的二进制文件。同样,您拥有的代码需要处于32位保护模式才能正常运行(因为它使用bits 32)。您给出的唯一要求是它在QEMU中运行,并且其中包含指令和/或数据的二进制文件不能被修改。
Multiboot规范允许您使用带有适当Multiboot头的简单ELF文件。在这里使用多引导是最好的选择,因为它已经设置了32位保护模式;启用A20线路;并将数据和代码加载到存储器中。这是一个很大的工作,你不必自己编码。
您可以做的是在NASM中创建一个Multiboot Package 器,其中包含一个简单的Multiboot头,并包含包含代码的二进制文件。下面的示例将创建一个兼容Multiboot的ELF可执行文件,它在内存中的头文件为0x 100000,内核文件为0x 101000。此代码使用NASM的incbin指令将一个二进制文件直接包含到包含Multiboot头和入口点的汇编文件中。

mboot.asm

  1. bits 32
  2. global _start
  3. MB1_MAGIC equ 0x1badb002
  4. MB1_FLAGS equ 0x00000000
  5. MB1_CHECKSUM equ -(MB1_MAGIC+MB1_FLAGS)
  6. section .data
  7. align 4
  8. dd MB1_MAGIC
  9. dd MB1_FLAGS
  10. dd MB1_CHECKSUM
  11. section .text
  12. _start:
  13. incbin "kernel.bin"

字符串

kernel.asm

  1. bits 32
  2. start:
  3. mov dword [0xb8000], 0x2f4b2f4f
  4. hlt


首先,你必须将kernel.asm构建为一个名为kernel.bin的二进制文件,其中:

  1. nasm -fbin kernel.asm -o kernel.bin


然后你必须使用以下命令组装multiboot wrapper:

  1. nasm -felf32 mboot.asm -o mboot.o


最后,将其链接到名为kernel.elf的ELF可执行文件:

  1. ld -Ttext=0x101000 -Tdata=0x100000 -melf_i386 mboot.o -o kernel.elf


这个兼容Multiboot的ELF可执行文件可以在QEMU中使用-kernel选项运行,如下所示:

  1. qemu-system-i386 -kernel kernel.elf


运行时的输出应类似于:
x1c 0d1x的数据

补充说明

随着内核二进制文件的增长,您需要通过标签引用绝对内存位置,您需要告诉NASM加载内核二进制文件的虚拟内存地址(VMA)为0x 101000。这可以使用ORG指令来完成,如下所示:

  1. bits 32
  2. org 0x101000
  3. start:
  4. mov dword [0xb8000], 0x2f4b2f4f
  5. hlt


这段代码不需要ORG指令,因为它不需要代码中任何地方的标签的绝对地址,但这可能会随着代码的扩展而改变。如果您没有正确指定ORG(原点),将无法工作的代码示例如下:

  1. ; Example program that uses an absolute reference to a label
  2. ; that won't work unless a proper ORG is used. Removing the ORG
  3. ; or using the wrong value will cause the code to not work as
  4. ; expected
  5. org 0x101000
  6. bits 32
  7. start:
  8. mov eax, [okmsg] ; Using an absolute reference to a label
  9. mov dword [0xb8000], eax ; Write value to display
  10. hlt
  11. okmsg: dd 0x2f4b2f4f


正如在回答的开头所述,我并不真的推荐这种方法,但我提供了一个解决方案,允许您运行您提交的代码,这些代码被放置在一个您说不能更改的二进制文件中。

加载并运行保护模式内核的软盘 Bootstrap

如果你不想使用Multiboot,而想创建一个磁盘映像,这个相当简化的 Bootstrap :

  • 启用A20线路
  • 将代码/数据从磁盘加载到内存,从0x 00008000开始
  • 进入32位保护模式
  • 执行代码

这段代码是基于我的一些其他答案的代码。一个是bootloader,它以16位真实的模式执行代码。我修改了它的代码,我用在一个问题,enables A20和进入保护模式。

** Boot .asm**

  1. STAGE2_ABS_ADDR equ 0x08000
  2. STAGE2_RUN_SEG equ 0x0000
  3. STAGE2_RUN_OFS equ STAGE2_ABS_ADDR
  4. ; Run stage2 with segment of 0x0000 and offset of 0x8000
  5. STAGE2_LOAD_SEG equ STAGE2_ABS_ADDR>>4
  6. ; Segment to start reading Stage2 into
  7. ; right after bootloader
  8. STAGE2_LBA_START equ 1 ; Logical Block Address(LBA) Stage2 starts on
  9. ; LBA 1 = sector after boot sector
  10. STAGE2_LBA_END equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
  11. ; Logical Block Address(LBA) Stage2 ends at
  12. DISK_RETRIES equ 3 ; Number of times to retry on disk error
  13. bits 16
  14. ORG 0x7c00
  15. ; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
  16. %ifdef WITH_BPB
  17. %include "bpb.inc"
  18. %endif
  19. boot_continue:
  20. xor ax, ax ; DS=SS=0 for stage2 loading
  21. mov ds, ax
  22. mov ss, ax ; Stack at 0x0000:0x7c00
  23. mov sp, 0x7c00
  24. cld ; Set string instructions to use forward movement
  25. ; Read Stage2 1 sector at a time until stage2 is completely loaded
  26. load_stage2:
  27. mov [bootDevice], dl ; Save boot drive
  28. mov di, STAGE2_LOAD_SEG ; DI = Current segment to read into
  29. mov si, STAGE2_LBA_START ; SI = LBA that stage2 starts at
  30. jmp .chk_for_last_lba ; Check to see if we are last sector in stage2
  31. .read_sector_loop:
  32. mov bp, DISK_RETRIES ; Set disk retry count
  33. call lba_to_chs ; Convert current LBA to CHS
  34. mov es, di ; Set ES to current segment number to read into
  35. xor bx, bx ; Offset zero in segment
  36. .retry:
  37. mov ax, 0x0201 ; Call function 0x02 of int 13h (read sectors)
  38. ; AL = 1 = Sectors to read
  39. int 0x13 ; BIOS Disk interrupt call
  40. jc .disk_error ; If CF set then disk error
  41. .success:
  42. add di, 512>>4 ; Advance to next 512 byte segment (0x20*16=512)
  43. inc si ; Next LBA
  44. .chk_for_last_lba:
  45. cmp si, STAGE2_LBA_END ; Have we reached the last stage2 sector?
  46. jl .read_sector_loop ; If we haven't then read next sector
  47. .stage2_loaded:
  48. mov si, noa20_err ; Default error message to A20 enable error
  49. call a20_enable ; Enable A20 line
  50. jz error_print ; If the A20 line isn't enabled, print error and stop
  51. lgdt [gdtr] ; Load GDT for 32-bit protected mode
  52. cli ; Disable interrupts since we don't have an IDT setup
  53. mov eax, cr0 ; Read CR0 register
  54. or eax, 1 ; Enable protected mode flage (bit 0)
  55. mov cr0, eax ; Set CR0 register&enter quasi 16-bit protected mode
  56. jmp CODE32_SEL:start32pm ; FAR JMP to use a 32-bit code selector
  57. ; This enters 32-bit protected mode @ start32pm
  58. .disk_error:
  59. xor ah, ah ; Int13h/AH=0 is drive reset
  60. int 0x13
  61. dec bp ; Decrease retry count
  62. jge .retry ; If retry count not exceeded then try again
  63. disk_error_end:
  64. ; Unrecoverable error; print drive error; enter infinite loop
  65. mov si, diskErrorMsg ; Display disk error message
  66. error_print:
  67. call print_string
  68. cli
  69. error_loop:
  70. hlt
  71. jmp error_loop
  72. ; Function: print_string
  73. ; Display a string to the console on display page 0
  74. ;
  75. ; Inputs: SI = Offset of address to print
  76. ; Clobbers: AX, BX, SI
  77. print_string:
  78. mov ah, 0x0e ; BIOS tty Print
  79. xor bx, bx ; Set display page to 0 (BL)
  80. jmp .getch
  81. .repeat:
  82. int 0x10 ; print character
  83. .getch:
  84. lodsb ; Get character from string
  85. test al,al ; Have we reached end of string?
  86. jnz .repeat ; if not process next character
  87. .end:
  88. ret
  89. ; Function: lba_to_chs
  90. ; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
  91. ;
  92. ; Resources: http://www.ctyme.com/intr/rb-0607.htm
  93. ; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
  94. ; https://stackoverflow.com/q/45434899/3857942
  95. ; Sector = (LBA mod SPT) + 1
  96. ; Head = (LBA / SPT) mod HEADS
  97. ; Cylinder = (LBA / SPT) / HEADS
  98. ;
  99. ; Inputs: SI = LBA
  100. ; Outputs: DL = Boot Drive Number
  101. ; DH = Head
  102. ; CH = Cylinder (lower 8 bits of 10-bit cylinder)
  103. ; CL = Sector/Cylinder
  104. ; Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
  105. ; Sector in lower 6 bits of CL
  106. ;
  107. ; Notes: Output registers match expectation of Int 13h/AH=2 inputs
  108. ;
  109. lba_to_chs:
  110. push ax ; Preserve AX
  111. mov ax, si ; Copy LBA to AX
  112. xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
  113. div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
  114. mov cl, dl ; CL = S = LBA mod SPT
  115. inc cl ; CL = S = (LBA mod SPT) + 1
  116. xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
  117. div word [numHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
  118. mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
  119. mov dl, [bootDevice] ; boot device, not necessary to set but convenient
  120. mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
  121. shl ah, 6 ; Store upper 2 bits of 10-bit Cylinder into
  122. or cl, ah ; upper 2 bits of Sector (CL)
  123. pop ax ; Restore scratch registers
  124. ret
  125. ; Function: wait_8042_cmd
  126. ; Wait until the Input Buffer Full bit in the keyboard controller's
  127. ; status register becomes 0. After calls to this function it is
  128. ; safe to send a command on Port 0x64
  129. ;
  130. ; Inputs: None
  131. ; Clobbers: AX
  132. ; Returns: None
  133. KBC_STATUS_IBF_BIT EQU 1
  134. wait_8042_cmd:
  135. in al, 0x64 ; Read keyboard controller status register
  136. test al, 1 << KBC_STATUS_IBF_BIT
  137. ; Is bit 1 (Input Buffer Full) set?
  138. jnz wait_8042_cmd ; If it is then controller is busy and we
  139. ; can't send command byte, try again
  140. ret ; Otherwise buffer is clear and ready to send a command
  141. ; Function: wait_8042_data
  142. ; Wait until the Output Buffer Empty (OBE) bit in the keyboard controller's
  143. ; status register becomes 0. After a call to this function there is
  144. ; data available to be read on port 0x60.
  145. ;
  146. ; Inputs: None
  147. ; Clobbers: AX
  148. ; Returns: None
  149. KBC_STATUS_OBE_BIT EQU 0
  150. wait_8042_data:
  151. in al, 0x64 ; Read keyboard controller status register
  152. test al, 1 << KBC_STATUS_OBE_BIT
  153. ; Is bit 0 (Output Buffer Empty) set?
  154. jz wait_8042_data ; If not then no data waiting to be read, try again
  155. ret ; Otherwise data is ready to be read
  156. ; Function: a20_kbd_enable
  157. ; Enable the A20 line via the keyboard controller
  158. ;
  159. ; Inputs: None
  160. ; Clobbers: AX, CX
  161. ; Returns: None
  162. a20_kbd_enable:
  163. pushf
  164. cli ; Disable interrupts
  165. call wait_8042_cmd ; When controller ready for command
  166. mov al, 0xad ; Send command 0xad (disable keyboard).
  167. out 0x64, al
  168. call wait_8042_cmd ; When controller ready for command
  169. mov al, 0xd0 ; Send command 0xd0 (read output port)
  170. out 0x64, al
  171. call wait_8042_data ; Wait until controller has data
  172. in al, 0x60 ; Read data from keyboard
  173. mov cx, ax ; CX = copy of byte read
  174. call wait_8042_cmd ; Wait until controller is ready for a command
  175. mov al, 0xd1
  176. out 0x64, al ; Send command 0xd1 (write output port)
  177. call wait_8042_cmd ; Wait until controller is ready for a command
  178. mov ax, cx
  179. or al, 1 << 1 ; Write value back with bit 1 set
  180. out 0x60, al
  181. call wait_8042_cmd ; Wait until controller is ready for a command
  182. mov al, 0xae
  183. out 0x64, al ; Write command 0xae (enable keyboard)
  184. call wait_8042_cmd ; Wait until controller is ready for command
  185. popf ; Restore flags including interrupt flag
  186. ret
  187. ; Function: a20_fast_enable
  188. ; Enable the A20 line via System Control Port A
  189. ;
  190. ; Inputs: None
  191. ; Clobbers: AX
  192. ; Returns: None
  193. a20_fast_enable:
  194. in al, 0x92 ; Read System Control Port A
  195. test al, 1 << 1
  196. jnz .finished ; If bit 1 is set then A20 already enabled
  197. or al, 1 << 1 ; Set bit 1
  198. and al, ~(1 << 0) ; Clear bit 0 to avoid issuing a reset
  199. out 0x92, al ; Send Enabled A20 and disabled Reset to control port
  200. .finished:
  201. ret
  202. ; Function: a20_bios_enable
  203. ; Enable the A20 line via the BIOS function Int 15h/AH=2401
  204. ;
  205. ; Inputs: None
  206. ; Clobbers: AX
  207. ; Returns: None
  208. a20_bios_enable:
  209. mov ax, 0x2401 ; Int 15h/AH=2401 enables A20 on BIOS with this feature
  210. int 0x15
  211. ret
  212. ; Function: a20_check
  213. ; Determine if the A20 line is enabled or disabled
  214. ;
  215. ; Inputs: None
  216. ; Clobbers: AX, CX, ES
  217. ; Returns: ZF=1 if A20 enabled, ZF=0 if disabled
  218. a20_check:
  219. pushf ; Save flags so Interrupt Flag (IF) can be restored
  220. push ds ; Save volatile registers
  221. push si
  222. push di
  223. cli ; Disable interrupts
  224. xor ax, ax
  225. mov ds, ax
  226. mov si, 0x600 ; 0x0000:0x0600 (0x00600) address we will test
  227. mov ax, 0xffff
  228. mov es, ax
  229. mov di, 0x610 ; 0xffff:0x0610 (0x00600) address we will test
  230. ; The physical address pointed to depends on whether
  231. ; memory wraps or not. If it wraps then A20 is disabled
  232. mov cl, [si] ; Save byte at 0x0000:0x0600
  233. mov ch, [es:di] ; Save byte at 0xffff:0x0610
  234. mov byte [si], 0xaa ; Write 0xaa to 0x0000:0x0600
  235. mov byte [es:di], 0x55 ; Write 0x55 to 0xffff:0x0610
  236. xor ax, ax ; Set return value 0
  237. cmp byte [si], 0x55 ; If 0x0000:0x0600 is 0x55 and not 0xaa
  238. je .disabled ; then memory wrapped because A20 is disabled
  239. dec ax ; A20 Disable, set AX to -1
  240. .disabled:
  241. ; Cleanup by restoring original bytes in memory. This must be in reverse
  242. ; order from the order they were originally saved
  243. mov [es:di], ch ; Restore data saved data to 0xffff:0x0610
  244. mov [si], cl ; Restore data saved data to 0x0000:0x0600
  245. pop di ; Restore non-volatile registers
  246. pop si
  247. pop ds
  248. popf ; Restore Flags (including IF)
  249. test al, al ; Return ZF=1 if A20 enabled, ZF=0 if disabled
  250. ret
  251. ; Function: a20_enable
  252. ; Enable the A20 line
  253. ;
  254. ; Inputs: None
  255. ; Clobbers: AX, BX, CX, DX
  256. ; Returns: ZF=0 if A20 not enabled, ZF=1 if A20 enabled
  257. a20_enable:
  258. call a20_check ; Is A20 already enabled?
  259. jnz .a20_on ; If so then we're done ZF=1
  260. call a20_bios_enable ; Try enabling A20 via BIOS
  261. call a20_check ; Is A20 now enabled?
  262. jnz .a20_on ; If so then we're done ZF=1
  263. call a20_kbd_enable ; Try enabling A20 via keyboard controller
  264. call a20_check ; Is A20 now enabled?
  265. jnz .a20_on ; If so then we're done ZF=1
  266. call a20_fast_enable ; Try enabling A20 via fast method
  267. call a20_check ; Is A20 now enabled?
  268. jnz .a20_on ; If so then we're done ZF=1
  269. .a20_err:
  270. xor ax, ax ; If A20 disabled then return with ZF=0
  271. .a20_on:
  272. ret
  273. bits 32
  274. start32pm:
  275. mov ax, DATA32_SEL ; Set up the 32-bit data selectors
  276. mov ds, ax
  277. mov es, ax
  278. mov fs, ax
  279. mov gs, ax
  280. ; Zero extend SP to ESP. SP is already at 0x7c00
  281. ; DL still contains the boot drive number
  282. movzx esp, sp
  283. ; Execute stage2 code
  284. jmp STAGE2_RUN_OFS
  285. ; 32-bit GDT for protected mode
  286. ; Macro to build a GDT descriptor entry
  287. %define MAKE_GDT_DESC(base, limit, access, flags) \
  288. (((base & 0x00FFFFFF) << 16) | \
  289. ((base & 0xFF000000) << 32) | \
  290. (limit & 0x0000FFFF) | \
  291. ((limit & 0x000F0000) << 32) | \
  292. ((access & 0xFF) << 40) | \
  293. ((flags & 0x0F) << 52))
  294. ; GDT structure
  295. gdt_start:
  296. dq MAKE_GDT_DESC(0, 0, 0, 0); null descriptor
  297. gdt32_code:
  298. dq MAKE_GDT_DESC(0, 0x000fffff, 10011010b, 1100b)
  299. ; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
  300. gdt32_data:
  301. dq MAKE_GDT_DESC(0, 0x000fffff, 10010010b, 1100b)
  302. ; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
  303. gdt_end:
  304. CODE32_SEL equ gdt32_code - gdt_start
  305. DATA32_SEL equ gdt32_data - gdt_start
  306. ; GDT record
  307. align 4
  308. dw 0 ; Padding align dd GDT in gdtr on 4 byte boundary
  309. gdtr:
  310. dw gdt_end - gdt_start - 1
  311. ; limit (Size of GDT - 1)
  312. dd gdt_start ; base of GDT
  313. ; If not using a BPB (via bpb.inc) provide default Heads and SPT values
  314. %ifndef WITH_BPB
  315. numHeads: dw 2 ; 1.44MB Floppy has 2 heads & 18 sector per track
  316. sectorsPerTrack: dw 18
  317. %endif
  318. bootDevice: db 0x00
  319. diskErrorMsg: db "Unrecoverable disk error!", 0
  320. noa20_err: db "A20 line couldn't be enabled", 10, 13, 0
  321. ; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
  322. TIMES 510-($-$$) db 0
  323. dw 0xaa55
  324. ; Beginning of stage2. This is at 0x8000 and will allow your stage2 to be 32.5KiB
  325. ; before running into problems. DL will be set to the drive number originally
  326. ; passed to us by the BIOS.
  327. NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
  328. ; Number of 512 byte sectors stage2 uses.
  329. stage2_start:
  330. ; Insert stage2 binary here. It is done this way since we
  331. ; can determine the size(and number of sectors) to load since
  332. ; Size = stage2_end-stage2_start
  333. incbin "kernel.bin"
  334. ; End of stage2. Make sure this label is LAST in this file!
  335. stage2_end:
  336. ; Fill out this file to produce a 1.44MB floppy image
  337. TIMES 1024*1440-($-$$) db 0x00

首先根据您在kernel.asm中的代码构建kernel.bin

  1. nasm -f bin kernel.asm -o kernel.bin


创建一个1.44MiB软盘(disk.img),其中包含kernel.bin中的代码和数据以及将内核读入内存的卷 Boot 记录(VBR):

  1. nasm -f bin boot.asm -o disk.img


它可以从软盘映像在QEMU中运行,使用:

  1. qemu-system-i386 -fda disk.img


这个版本的代码最终可能需要在内核中使用ORG 0x8000,而不是我前面介绍的Multiboot版本,在您继续开发内核时,它可能需要ORG 0x101000。如果您没有正确指定ORG(原点),将无法工作的代码示例如下:

  1. ; Example program that uses an absolute reference to a label
  2. ; that won't work unless a proper ORG is used. Removing the ORG
  3. ; or using the wrong value will cause the code to not work as
  4. ; expected
  5. org 0x8000
  6. bits 32
  7. start:
  8. mov eax, [okmsg] ; Using an absolute reference to a label
  9. mov dword [0xb8000], eax ; Write value to display
  10. hlt
  11. okmsg: dd 0x2f4b2f4f

展开查看全部

相关问题