大多数人在高校里面学的第一门汇编语言是基于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调用相应库函数的机器指令所“填充”,目标代码等于只是做了一个预调用的“标记”。