天天看點

在Windows 10上将C語言程式轉成16位8086彙編代碼

大多數人在高校裡面學的第一門彙編語言是基于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所示。
在Windows 10上将C語言程式轉成16位8086彙編代碼

圖1 TC 2.0安裝目錄

為了之後能夠操作成功,建議對tc/include下的stdio.h檔案做如下修改:
在Windows 10上将C語言程式轉成16位8086彙編代碼

圖2 修改檔案

如圖2所示,即将tc/include/stdio.h第30行的
改為
因為上面的代碼隻包含了stdio.h頭檔案,是以部落客隻針對包含stdio.h會産生的問題進行了解決。(可能還有其他坑,哈哈哈!)
修改檔案完成後,打開DosBox,進入tc檔案夾,運作tcc.exe将C程式彙編為16位的8086彙編指令。在這之前需先将C檔案放置在與tcc.exe同一目錄下。執行的過程與結果如圖3至圖6所示。
在Windows 10上将C語言程式轉成16位8086彙編代碼

圖3 使用TCC生成彙編代碼

在Windows 10上将C語言程式轉成16位8086彙編代碼

圖4 TCC 2.0所生成的彙編代碼

在Windows 10上将C語言程式轉成16位8086彙編代碼

圖5 編譯所得彙編代碼

在Windows 10上将C語言程式轉成16位8086彙編代碼

圖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調用相應庫函數的機器指令所“填充”,目标代碼等于隻是做了一個預調用的“标記”。

繼續閱讀