大多數人在高校裡面學的第一門彙編語言是基于16位的Intel 8086處理器(即8086彙編語言),現在的大多數系統都是32或者64位的,為了實驗需要我們一般安裝DosBox來作為16位DOS系統模拟器。
計算機類專業一般都會有編譯原理的課,課程會詳細介紹代碼編譯的各個階段:詞法分析、文法分析、語義分析、中間代碼産生、編譯優化與目标代碼生成。其中,在“目标代碼生成”階段,現代編譯器一般是生成一個彙編檔案,部落客想要擷取的就是基于16位 DOS系統的8086格式的彙編檔案。
GCC與VS等主流的編譯器都提供了在編譯C/C++代碼過程中生成彙編指令的功能,詳細步驟非常簡單,在此不贅述。下面是C語言源代碼以及其通過VS 2019與GCC分别生成的彙編指令。
C語言源碼:
#include <stdio.h>
int main()
{
int i, j, n;
for (i = 1; i <= 9; i++)
{
for (j = 1; j <= i; j++)
printf("%d × %d = %2d ", i, j, i * j);
printf("\n");
}
return 0;
}
VS 2019生成彙編:
; Listing generated by Microsoft (R) Optimizing Compiler Version 19.26.28806.0
TITLE c:\users\hadoop001\desktop\vs2019\main2\main2.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB OLDNAMES
PUBLIC ??_C@[email protected]@?$CFd?5?C?$JH?5?$CFd?5?$DN?5?$CF2d?5?5@ ; `string'
PUBLIC ??_C@_01EEMJAFIK@?6@ ; `string'
EXTRN __imp____stdio_common_vfprintf:PROC
EXTRN __imp____acrt_iob_func:PROC
EXTRN @[email protected]:PROC
; COMDAT ??_C@_01EEMJAFIK@?6@
CONST SEGMENT
??_C@_01EEMJAFIK@?6@ DB 0aH, 00H ; `string'
CONST ENDS
; COMDAT ??_C@[email protected]@?$CFd?5?C?$JH?5?$CFd?5?$DN?5?$CF2d?5?5@
CONST SEGMENT
??_C@[email protected]@?$CFd?5?C?$JH?5?$CFd?5?$DN?5?$CF2d?5?5@ DB '%d ', 0c3H
DB 097H, ' %d = %2d ', 00H ; `string'
CONST ENDS
PUBLIC _main
PUBLIC _printf
PUBLIC __vfprintf_l
PUBLIC ___local_stdio_printf_options
COMM ?_OptionsStorage@?1??__local_stdio_printf_options@@[email protected]:QWORD ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_DATA ENDS
; Function compile flags: /Ogtp
; File C:\Users\hadoop001\Desktop\VS2019\main2\main2.c
; COMDAT _main
_TEXT SEGMENT
_main PROC ; COMDAT
; 5 : {
push ebx
push esi
push edi
; 6 : int i, j, n;
; 7 : for (i = 1; i <= 9; i++)
mov esi, 1
[email protected]:
; 8 : {
; 9 : for (j = 1; j <= i; j++)
mov edi, 1
mov ebx, esi
npad 1
[email protected]:
; 10 : printf("%d × %d = %2d ", i, j, i * j);
push ebx
push edi
push esi
push OFFSET ??_C@[email protected]@?$CFd?5?C?$JH?5?$CFd?5?$DN?5?$CF2d?5?5@
call _printf
inc edi
add esp, 16 ; 00000010H
add ebx, esi
cmp edi, esi
jle SHORT [email protected]
; 11 : printf("\n");
push OFFSET ??_C@_01EEMJAFIK@?6@
call _printf
inc esi
add esp, 4
cmp esi, 9
jle SHORT [email protected]
; 12 : }
; 13 :
; 14 : return 0;
pop edi
pop esi
xor eax, eax
pop ebx
; 15 : }
ret 0
_main ENDP
_TEXT ENDS
END
GCC生成彙編:
.file "main1.c"
.text
.def __main; .scl 2; .type 32; .endef
.section .rdata,"dr"
.LC0:
.ascii "%d \303\227 %d = %2d \0"
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $48, %rsp
.seh_stackalloc 48
.seh_endprologue
call __main
movl $1, -4(%rbp)
jmp .L2
.L5:
movl $1, -8(%rbp)
jmp .L3
.L4:
movl -4(%rbp), %eax
imull -8(%rbp), %eax
movl %eax, %edx
movl -8(%rbp), %ecx
movl -4(%rbp), %eax
movl %edx, %r9d
movl %ecx, %r8d
movl %eax, %edx
leaq .LC0(%rip), %rcx
call printf
incl -8(%rbp)
.L3:
movl -8(%rbp), %eax
cmpl -4(%rbp), %eax
jle .L4
movl $10, %ecx
call putchar
incl -4(%rbp)
.L2:
cmpl $9, -4(%rbp)
jle .L5
movl $0, %eax
addq $48, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (GNU) 10.1.0"
.def printf; .scl 2; .type 32; .endef
.def putchar; .scl 2; .type 32; .endef
可能是先入為主,我一直好奇:8086彙編表示的目标代碼會是什麼樣子的?之前也試了不少方法,均告失敗,昨天一頓操作居然成功了。首先安裝DosBox,之後下載下傳TCC 2.0 for DOS(TCC 2.0 for DOS下載下傳位址(侵删!)),将解壓後的tc檔案夾放在DosBox的安裝目錄下,如圖1所示。
圖1 TC 2.0安裝目錄
為了之後能夠操作成功,建議對tc/include下的stdio.h檔案做如下修改:
圖2 修改檔案
如圖2所示,即将tc/include/stdio.h第30行的
改為
因為上面的代碼隻包含了stdio.h頭檔案,是以部落客隻針對包含stdio.h會産生的問題進行了解決。(可能還有其他坑,哈哈哈!)
修改檔案完成後,打開DosBox,進入tc檔案夾,運作tcc.exe将C程式彙編為16位的8086彙編指令。在這之前需先将C檔案放置在與tcc.exe同一目錄下。執行的過程與結果如圖3至圖6所示。
圖3 使用TCC生成彙編代碼
圖4 TCC 2.0所生成的彙編代碼
圖5 編譯所得彙編代碼
圖6 連結所得彙編代碼
用到的指令總結如下:
tcc -S -ml main1.c
copy main1.asm ..
cd ..
masm main1.asm
link main1.obj
最終得到的彙編代碼如下所示:
ifndef ??version
?debug macro
endm
endif
?debug S "main1.c"
MAIN1_TEXT segment byte public 'CODE'
DGROUP group _DATA,_BSS
assume cs:MAIN1_TEXT,ds:DGROUP
MAIN1_TEXT ends
_DATA segment word public 'DATA'
d@ label byte
[email protected] label word
_DATA ends
_BSS segment word public 'BSS'
b@ label byte
[email protected] label word
?debug C E95359CD50076D61696E312E63
?debug C E9B434CD500F696E636C7564652F737464696F2E68
?debug C E900501D1110696E636C7564652F7374646172672E68
_BSS ends
MAIN1_TEXT segment byte public 'CODE'
; ?debug L 3
_main proc far
push si
push di
; ?debug L 6
mov si,1
jmp short @5
@4:
; ?debug L 8
mov di,1
jmp short @9
@8:
; ?debug L 9
mov ax,si
mul di
push ax
push di
push si
push ds
mov ax,offset DGROUP:s@
push ax
call far ptr _printf
add sp,10
@7:
inc di
@9:
cmp di,si
jle @8
@6:
; ?debug L 10
push ds
mov ax,offset DGROUP:s@+17
push ax
call far ptr _printf
pop cx
pop cx
@3:
inc si
@5:
cmp si,9
jle @4
@2:
; ?debug L 13
xor ax,ax
jmp short @1
@1:
; ?debug L 14
pop di
pop si
ret
_main endp
MAIN1_TEXT ends
?debug C E9
_DATA segment word public 'DATA'
s@ label byte
db 37
db 100
db 32
db 195
db 151
db 32
db 37
db 100
db 32
db 61
db 32
db 37
db 50
db 100
db 32
db 32
db 0
db 10
db 0
_DATA ends
extrn _printf:far
MAIN1_TEXT segment byte public 'CODE'
MAIN1_TEXT ends
public _main
end
從圖5與圖6可以看出,TCC所生成的8086彙編代碼可以通過MASM的彙編,但無法通過LINK的連結,原因是因為C源程式中調用了庫函數printf,其在最終生成可執行檔案時才會被OS調用相應庫函數的機器指令所“填充”,目标代碼等于隻是做了一個預調用的“标記”。