程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26

程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26



通过本文能学到什么?

  • NASM的条件汇编
  • 用NASM编译的时候,通过命令行选项定义宏
  • Makefile的条件语句
  • 在make命令行中覆盖Makefile中的变量值
  • 第13章习题解答
  • 复习如何构造栈段描述符


我们接着上篇博文说。

在我修改后的文件中,用到了条件汇编。

比如:

%ifdef DEBUG

    put_core_salt:  ;打印内核的符号
    ...
    ...
    put_usr_salt:  ;打印用户的符号
    ...
    ...
%endif

下文对此进行讲解。

1.条件汇编

与C预处理器相似,NASM允许对一段源代码只在某特定条件满足时进行汇编。

注意,C语言的预处理指令是由#字符开头的一些命令,NASM编译器的预处理指令由%开头。

1.1 只在某特定条件满足时进行汇编

%if<condition>
    ;if <condition>满足时接下来的代码被汇编。
    ......

%elif<condition2>
    ; 当 if<condition>不满足,而<condition2>满足时,该段代码被汇编。
    ......

%else
    ;当<condition>跟<condition2>都不满足时,该段代码被汇编。
    ......

%endif

%else%elif子句都是可选的,也可以使用多于一个的%elif子句。

1.2 测试单行宏是否存在

%ifdef DEBUG
    ...

%endif

如果我们定义了宏DEBUG,那么省略号处的代码就会被汇编,否则不会。

以定义宏DEBUG为例,可以用两种方法。

1.3 在代码中直接定义宏

%define DEBUG

1.4 通过命令行选项

编译文件的时候,用-d 宏名称

    -d DEBUG

例如:

 nasm c13_core.asm -o c13_core.bin -d DEBUG

注意:-d可以写成-D,它和后面宏名之间的空格也可以不要。

例如:

 nasm c13_core.asm -o c13_core.bin -dDEBUG

2.关于Makefile

因为加入了条件汇编,所以Makefile和上篇博文中的不太一样。

上篇博文地址:

程序的加载和执行(五)——《x86汇编语言:从实模式到保护模式》读书笔记25

修改后的Makefile如下。

DEBUG = 0

BIN = c13_mbr.bin c13_core.bin c13.bin empty
A_DIR = /home/cjy/a.img
C_DIR = /home/cjy/c.img

all:$(BIN)

.PHONY:all clean

c13_mbr.bin:c13_mbr.asm
    nasm $< -o [email protected]
    dd if=[email protected] of=$(A_DIR)

c13_core.bin:c13_core.asm
ifeq ($(DEBUG),1)
    nasm $< -o [email protected] -D DEBUG
else
    nasm $< -o [email protected]
endif
    dd if=[email protected] of=$(C_DIR) bs=512 seek=1 conv=notrunc

c13.bin:c13.asm
    nasm $< -o [email protected]
    dd if=[email protected] of=$(C_DIR) bs=512 seek=50 conv=notrunc

empty:diskdata.txt
    dd if=$< of=$(C_DIR) bs=512 seek=100 conv=notrunc
    touch [email protected]

clean:
    $(RM) $(BIN)

2.1 条件语句

首先,我们定义了一个变量(也被称作宏)DEBUG,并给其赋值为0;

需要注意的是以下几行:

ifeq ($(DEBUG),1)
    nasm $< -o [email protected] -D DEBUG
else
    nasm $< -o [email protected]
endif

这里用到了Makefile的条件语句。执行make时,会根据运行时的不同情况选择不同的执行分支。

在这个例子中,当变量DEBUG的值为1时,就执行nasm $< -o [email protected] -D DEBUG

当变量DEBUG的值不为1时,就执行nasm $< -o [email protected]

当我们想编译带调试信息的源文件,修改Makefile中DEBUG的值为1就可以了。

但是,还有没有更简单的方法呢?

2.2 在命令行中定义变量

当命令行中的变量(宏)定义跟makefile中的定义有冲突时,以命令行中的定义为准。所以,我们可以在执行make的时候加上变量=新值,以覆盖Makefile文件中的变量值。

对于本文的例子,除了修改Makefile中DEBUG的值为1这种方法外,还有一种方法是在命令行中重新给变量(宏)赋值。

make "DEBUG=1"

注意:当没有空格的时候,引号也可以省略。由于宏定义必须作为单个参数进行传递,所以要避免使用空格,所以更妥当的方法是使用引号。

3.第13章习题解答

在本章中,用户程序只给出建议的栈大小,但并不提供栈空间。现在,修改内核程序和用户程序,改由用户程序自行提供栈空间。要求:栈段必须定义在用户程序头部之后。

在这里,我给出自己的答案,供学习者参考。

3.1 对于源文件c13.asm的修改

修改有两处。第一处是:

由于栈空间由用户程序提供,所以必须指明栈段的汇编地址和大小,这样内核程序才能有足够的信息为用户创建栈段描述符。

第二处是:

因为题目已经要求栈段必须定义在用户程序头部之后,所以这里紧接着头部定义栈空间。

小插曲

编译这个文件,会报错。

c13.asm:14: error: division operator may only be applied to scalar values
make: *** [c13.bin] 错误 1

哦,这是为什么呢?我改成了

stack_len        dd (stack_end-0)/(4*1024) 

依然报同样的错误。

通过查资料,发现有的朋友是这样回答的:

A label is a relocatable value——its value is modified by the linker/loader.The difference between two labels(in the same section) is a scalar value, and NASM will work with it.

好吧,既然同一个section的两个标号的差值是标量,那我这样写好了:

 stack_len        dd (stack_end-stack_start)/(4*1024)  

其实stack_start这个标号之前是没有的,是我为了做差值专门加上的。

你还别说,这样写真的管用,不报错了。

3.2 对源文件c13_core.asm的修改

左边的代码(配书代码),内核为用户栈分配内存,然后计算栈的高端物理地址。

右边的代码(习题代码),内核不需要分配内存,仅仅从头部取出栈的起始地址,然后计算栈的高端物理地址。

这段代码是我半年前写的,可是现在读起来也觉得陌生了。那就解释一下最关键的3行,加深自己的记忆。

469      mov edx,edi
470      add edx,[edi+0x08]                ; 栈段起始的线性地址
471      add eax,edx                       ; 得到栈的高端物理地址 

此时,DS指向了0-4GB的数据段,EDI中的内容是用户程序的加载地址。

469:EDX中是用户程序的加载地址;

470:从用户头部偏移0x08处取得section.stack.start,加上用户程序的加载地址(EDX),就得到了用户栈的起始地址。如图所示。

471:栈段描述符中的基地址,应该是栈空间的高端物理地址,所以还要加上栈的大小(在EAX中)。

如果不明白为什么这样构造栈段描述符,可以参考我的博文:

如何构造栈段描述符

3.3 在Bochs中验证程序

我们的修改对吗?“实践出真知”。

3.3.1 验证思路

因为栈空间是用户程序提供的,内核只是根据头部信息创建对应的栈段描述符。所以我们要验证的就是栈段描述符和用户定义的栈是否相符。在上面提到的博文中,我已经详细推导了如何构造栈段描述符。

用户程序的头部结构如下图所示:

本实验的用户程序的符号表有3个符号,所以头部总长度为0x328;由于用户程序的加载地址是0x100000,所以栈空间的起始地址为0x100328;又因为栈空间大小定义为0x1000(4KB),所以栈空间的高端物理地址为0x100328+0x1000=0x101328

这应该就是栈段描述符的基地址。

有效段界限应为0xFFFFFFFF-栈的大小(以字节为单位),即0xFFFFFFFF-0x1000=0xFFFFEFFF,这应该是栈段描述符中的段界限。

3.3.2 验证结果

在Bochs中运行程序,跑起来后,Ctrl+C暂停程序,输入info gdt可以查看GTD的信息。我们看到在下图中:

黄色划线的描述符与我们的推理相符。所以,我们的修改是正确的。

【end】

时间: 2024-08-07 08:36:37

程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26的相关文章

程序的加载和执行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23

程序的加载和执行(三)--读书笔记23 接着上次的内容说. 关于过程load_relocate_program的讲解还没有完,还差创建栈段描述符和重定位符号表. 分配栈空间与创建栈段描述符 462 ;建立程序堆栈段描述符 463 mov ecx,[edi+0x0c] ;4KB的倍率 464 mov ebx,0x000fffff 465 sub ebx,ecx ;得到段界限 466 mov eax,4096 467 mul dword [edi+0x0c] 468 mov ecx,eax ;准备为

Linux0.11内核--加载可执行二进制文件之3.exec

最后剩下最核心的函数do_execve了,由于这里为了简单起见我不分析shell命令的情况, /* * 'do_execve()'函数执行一个新程序. */ //// execve()系统中断调用函数.加载并执行子进程(其它程序). // 该函数系统中断调用(int 0x80)功能号__NR_execve 调用的函数. // 参数:eip - 指向堆栈中调用系统中断的程序代码指针eip 处,参见kernel/system_call.s 程序 // 开始部分的说明:tmp - 系统中断调用本函数时

[转]JavaScript 的性能优化:加载和执行

原文链接:http://www.ibm.com/developerworks/cn/web/1308_caiys_jsload/index.html?ca=drs- JavaScript 的性能优化:加载和执行 蔡 愉晟, 软件工程师, IBM 简介: 随着 Web2.0 技术的不断推广,越来越多的应用使用 JavaScript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 JavaScript 的阻塞特性变的复杂,也就

【转】js JavaScript 的性能优化:加载和执行

JavaScript 的性能优化:加载和执行 转自:https://www.ibm.com/developerworks/cn/web/1308_caiys_jsload/ 随着 Web2.0 技术的不断推广,越来越多的应用使用 JavaScript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 JavaScript 的阻塞特性变的复杂,也就是说当浏览器在执行 JavaScript 代码时,不能同时做其他任何事情.本文详

jvm内存模型,java类从编译到加载到执行的过程,jvm内存分配过程

一.jvm内存模型 JVM 内存模型主要分为堆.程序计数器.方法区.虚拟机栈和本地方法栈 1.堆 1.1.堆是 JVM 内存中最大的一块内存空间. 1.2.该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中. 1.3.堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成. 2.程序计数器(Program Counter Register) 程序计数器是一块很小的内存

JavaScript 的性能优化:加载和执行

随着 Web2.0 技术的不断推广,越来越多的应用使用 JavaScript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 JavaScript 的阻塞特性变的复杂,也就是说当浏览器在执行 JavaScript 代码时,不能同时做其他任何事情.本文详细介绍了如何正确的加载和执行 JavaScript 代码,从而提高其在浏览器中的性能. 概览 无论当前 JavaScript 代码是内嵌还是在外链文件中,页面的下载和渲染都必

加载和执行

有阻塞的脚本   js脚本阻塞原因? 大多数浏览器使用单一进程处理用户界面更新(页面解析,页面渲染,用户交互)和JavaScript脚本代码执行.所以同一时刻只能做其中一件事.这样做是显而易见的,因为脚本代码很可能包含了dom处理的代码. 反应到代码上就是,当script标签出现时,无论代码是内嵌还是通过外链加载,都需要等待js代码加载(内部代码就直接读取)并执行完成,才能继续解析渲染页面. 注释:外部脚步有两个例外: 如果 async="async":脚本加载完成可用后,会相对于页面

对于HTML页面中CSS, JS, HTML的加载与执行过程的简单分析

最近在研究HTML页面中JavaScript的执行顺序问题.在JavaScript中,定义一个方法或者函数有很多方式,最常见的有2中,function语句式与函数直接量方式. 对于function语句式,解释器会优先解释.即加载了这个js文件后,会扫描一下所有的js代码,然后把该优先执行的东西先执行了,然后再从上到下按顺序执行.所以,定义的代码可以在执行的代码后边.就跟C#中的方法定义一样.解释器已经记住了这个方法,知道在内存中的哪里,用的时候直接去取就行了. C#语言是,对象中的属性与方法具有

怎么样加快JavaScript加载和执行效率

概览 无论当前 JavaScript 代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成.JavaScript 执行过程耗时越久,浏览器等待响应用户输入的时间就越长.浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或 JavaScript 的命名空间,它们对后面页面内容造成影响.一个典型的例子就是在页面中使用document.write(). JavaScript 代码内嵌示例 <html> <head> <title>Source