天天看点

在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调用相应库函数的机器指令所“填充”,目标代码等于只是做了一个预调用的“标记”。

继续阅读