1. 用户程序的结构:
1) 一般源程序都以段的形式进行组织,这样可以使逻辑更加清晰,在NASM中使用section关键字定义一个段,形式是:section 段名
2) 程序可以用段名来引用段,但是NASM编译器并不关心段的具体用途,或者说是根本不知道段的用途(代码段还是数据段等),同时NASM对段的数量也没有任何限制,如果代码中没有定一段则整个程序自成一段;
3) 定义段的同时可以定义段的一些属性,比如可以使用关键字align来定义段的对其方式,比如:section code align=16,这样就表示该段的其实地址是以16字节对齐的,即段的起始位置必须是16的整数倍;
!注意:该属性只影响段的起始位置的对其但不影响段的末尾对齐方式,事实上NASM也无法判断一个段的末尾,只有当遇到一个新的段的定义的时候才能知道前一个段结束了;
4) 段的起始位置:就是该段中第一行指令的地址(指令可以是普通指令也当然可以是数据定义指令db、dw等等;
5) 和加载程序之间的约定——应用程序头部Header:
i. 在有操作系统的环境下编译完一个程序之后编译器会隐式地、默认地添加一个应用程序头部(位置处于程序的起始位置处);
ii. 头部包含着加载器该如何加载该程序的一些信息,或者说是加载器和程序之间的某些约定或规范,而加载器通过这些信息将程序正确地加载进内存中;
iii. 在有操作系统的环境下,应用程序头部和加载器都是操作系统负责的,但是在这里我们模拟一下这个操作系统的工作,即手写完成加载器和程序头部来模拟操作系统的这两个功能;
2. 用户程序和加载器的简单实现:
用户程序:默认程序已经正确加载到了内存的空闲位置,并且从定义的标号start处该是执行程序,作用是将两个数据段中的字符串打印到屏幕上,并且处理回车和换行两个控制符
!注意用户程序头部的定义,里面包含了程序大小、程序开始执行的入口、程序中各个段的起始位置等信息;
!其中重定位表的作用就是:编译后各项保存的是各段在源程序中的绝对汇编地址,经加载程序加载后就将各项修改成在物理内存中实际的地址,因此称为重定位表;
app.nas,编译后生成app.bin
; 应用程序头
; 用于提供加载器相关加载信息
; 是应用程序规范的一部分
section header vstart=0
app_size dd app_end ; [APP_SIZE:0x00] 程序的大小(字节)
app_entry dw start ; [APP_ENTRY:0x04] 入口处偏移地址
app_entry_seg dd section.code1.start ; [APP_ENTRY_SEG:0x06] 入口处段地址
; section.段名.start是NASM提供的伪指令,用于段起始位置在源程序中的绝对汇编地址
; 绝对汇编地址是指相对于整个源程序头的偏移量,而整个程序头的绝对汇编地址是0
; 绝对汇编地址是一个32位无符号数,因此使用dd表示
c_realloc_tbl dw (tbl_end - tbl_start) / 4 ; [C_REALLOC_TBL:0x0A] 重定位表表项数目
tbl_start: ; [TBL_START:0x0C]
seg_addr_code1 dd section.code1.start
seg_addr_code2 dd section.code2.start
seg_addr_data1 dd section.data1.start
seg_addr_data2 dd section.data2.start
seg_addr_stack dd section.stack.start
tbl_end:
; section header end
;;
;;
section stack align=16 vstart=0
resb 256
stack_end:
; section stack end
;;
;;
section data1 align=16 vstart=0
msg0 db ' This is NASM - the famous Netwid