在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@_0BB@KPGCELHG@?$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	@__security_check_cookie@4:PROC
;	COMDAT ??_C@_01EEMJAFIK@?6@
CONST	SEGMENT
??_C@_01EEMJAFIK@?6@ DB 0aH, 00H			; `string'
CONST	ENDS
;	COMDAT ??_C@_0BB@KPGCELHG@?$CFd?5?C?$JH?5?$CFd?5?$DN?5?$CF2d?5?5@
CONST	SEGMENT
??_C@_0BB@KPGCELHG@?$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@@9@9: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
$LL4@main:

; 8    :     {
; 9    :         for (j = 1; j <= i; j++)

	mov	edi, 1
	mov	ebx, esi
	npad	1
$LL7@main:

; 10   :             printf("%d × %d = %2d  ", i, j, i * j);

	push	ebx
	push	edi
	push	esi
	push	OFFSET ??_C@_0BB@KPGCELHG@?$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 $LL7@main

; 11   :         printf("\n");

	push	OFFSET ??_C@_01EEMJAFIK@?6@
	call	_printf
	inc	esi
	add	esp, 4
	cmp	esi, 9
	jle	SHORT $LL4@main

; 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 DOSTCC 2.0 for DOS下載地址(侵刪!)),將解壓後的tc文件夾放在DosBox的安裝目錄下,如圖1所示。


圖1 TC 2.0安裝目錄



爲了之後能夠操作成功,建議對tc/include下的stdio.h文件做如下修改:


圖2 修改文件

如圖2所示,即將tc/include/stdio.h第30行的

#include        <stdarg.h>

改爲

#include        "include/stdarg.h"

因爲上面的代碼只包含了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
d@w	label	word
_DATA	ends
_BSS	segment word public 'BSS'
b@	label	byte
b@w	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調用相應庫函數的機器指令所“填充”,目標代碼等於只是做了一個預調用的“標記”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章