剖析重定位——程序加载器/vstart解析
本文通过汇编来阐述重定位的原理,不了解汇编的同学请先移步 汇编语言入门指南 。
本文参考李忠先生的《x86汇编语言:从实模式到保护模式》,若需了解详情,可移步本书(书上的例子较难,本文例子经过了简化)。
另外,本文仅在实模式下,通过汇编来描述重定位的基本过程,实际程序的重定位肯定更加复杂,如果想深刻了解程序的加载过程,请阅读神书《链接,装载与库》。
本文参考:《操作系统真相还原》《汇编语言第四版》《x86汇编语言:从实模式到保护模式》,程序的加载
需要注意的是,编译软件必须使用 nasm,不可使用 masm。原因是 nasm 可以生成 .bin 文件,.bin 文件是纯二进制文件,可以直接输入到 CPU 运行,不像 elf 或 pe 文件那样有许多描述信息。 可执行文件中包含描述信息和指令 ,这些描述信息就是我们重点要说的内容,而 masm 会自动生成描述信息,掩盖了这样过程,不利于我们探究重定位;相反,nasm 可以由我们自己来规划描述信息。废话不多说,让我们开始吧。
vstart 和 section.xxx.start 究竟是什么?
前置结论
很多朋友学习 nasm 时,都会 ...
汇编语言入门
什么是汇编语言?
我们最初学习编程时,一般都是学习高级语言,诸如 C++,Java,Python 等,通过一定语法编写代码,然后运行,代码就能够顺利地在电脑中跑起来。但是,计算机实际上并不认识高级语言,它只认识如 01011001 这样的二进制数字。0,1 虽然简单,但无数个代表着高低电平的 0 和 1 组合却能指挥计算机完成几乎所有你能想到的任务。
早期的程序是由科学家们手工编写二级制代码完成的,面对巨量的,毫无规律的 0 和 1,其工作量可想而知。为了解决这一窘况,汇编语言应运而生。其实, 汇编语言严格来说并不是一门语言,而仅仅只是一套助记符,它与二进制代码一一对应 。如下图,汇编语言与人类语言更为接近,便于阅读和记忆。
汇编语言直接运行于硬件之上 。由于 CPU 硬件设计和内部架构的不同,其对应的指令集(机器语言)也不同,每一种 CPU 都有自己的汇编指令集 。 所以,汇编语言依赖于硬件体系,不便于移植 。对于同一个程序,如果在这台机器上可以运行,而到另一台机器上就必须重新改写某些代码以适应机器,那这样就太麻烦了。再之,汇编代码只比机器代码容易阅读了一点而已,理解起来还是很困难 ...
云服务器配置clash
天下苦墙久矣~
这两天在云主机上搞事情,各种千奇百怪的问题,没想到最后原因查出来最多的居然是网络问题,F**K… 一怒之下我决定为云主机安装 clash 代理,还投入了 40 元巨资买月卡。开启代理后,10 分钟内解决了卡我一整天的问题,喜极而泣。
云主机安装 clash 的过程直接傻瓜式参考clash-on-linux配置 ,下面针对此文章做补充,以防后面迁移主机需要 。
查看云主机架构:
12dpkg --print-architectureamd64
本机架构为 amd64,所以 clash 必须下载对应的架构。点击此处下载 clash 。
原文的 MMDB 链接已经丢失,点击此处下载 MMDB 。
后面的图形化设置暂无需求。
基数排序
算法解析
和计数排序一样,基数排序也是一种 非比较 的排序。其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于它不是基于比较的排序,所以其复杂度突破了比较排序的下限 O(NlogN)O(NlogN)O(NlogN) ,达到了 O(k∗N)O(k*N)O(k∗N) ,其中 k 是最大数字的位数,N 是待排序数字的个数。基数排序也同样用到了桶的思想,下面我们来看看它是如何进行排序的:
不难看出其排序过程:先以个位数为标准进行排序,在此基础上又以十位数为标准进行排序。这个过程很好理解,就相当于将 10~19 的数分在一组,20~29 的数分在一组,显然前组都小于后组,然后组内又排序,此时由于十位都相同,所以只需比较个位即可。
同时发现,我们需要开辟十个桶(0~9)来完成排序,但这样又耗费了较大的空间。实际上有一种更加优雅的方法,只需要开辟两个数组就能完成基数排序,让我们来看看具体是如何操作的。
先给出一个待排序数组(同图1):
然后创建一个 count 辅助数组,并统计个位数字的个数:
然后将 count 数组改成前缀和的形式,得到 count' 数组:
然后我们在 ...
希尔排序
文章参考:知乎-冒泡 、wiki 、chatGPT、知乎-helloCode
希尔排序是一种不稳定排序算法,最坏复杂度为 O(N^2^) ,但它是插入排序的改进版本,是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位,即每次只能消除一个逆序对
假设我们要从小到大排序,一个数组中取两个元素如果前面比后面大,则为一个逆序,容易看出排序的本质就是消除逆序数,可以证明对于随机数组,逆序数是O(N^2^) 的,而如果采用交换相邻元素的办法来消除逆序,每次正好只消除一个,因此必须执行O(N^2^)的交换次数,这就是为啥冒泡、选择、插入等算法只能到平方级别的原因,反过来,基于交换元素的排序要想突破这个下界,必须执行一些比较,交换相隔比较远的元素,使得一次交换能消除一个以上的逆序,希尔、快排、堆排等等算法都是交换比较远的元素,只不过规则各不同罢了。
——引自知乎-冒泡
这段总结真是醍醐灌顶!!!不过有个问题,插入排序并非交换相邻元素,为什么其复杂度仍为 O(N ...
入坑vim
插入文本
vim -R filename
把指定的文件以只读方式放入 Vim 编辑器中
i
在当前光标所在位置插入随后输入的文本,光标后的文本相应向右移动
I
在光标所在行的行首插入随后输入的文本
o
在光标所在行的下面插入新的一行。光标停在空行首,等待输入文本
O
在光标所在行的上面插入新的一行。光标停在空行的行首,等待输入文本
a
在当前光标所在位置之后插入随后输入的文本
A
在光标所在行的行尾插入随后输入的文本
查找文本
/abc
从光标所在位置向前查找字符串 abc
/^abc
查找以 abc 为行首的行 (^表示行首)
/abc$
查找以 abc 为行尾的行 ($表示行尾)
?abc
从光标所在向后查找字符串 abc
n
向同一方向重复上次的查找指令
N
向相反方向重复上次的查找指定
:set ic
忽略大小写
:set noic
不忽略大小写
替换文本
r
替换光标所在位置的字符
R
从光标所在位置开始替换字符,其输入内容会覆盖掉后面等长的文本内容,按“Esc ...
C++模板问题小记
写排序函数时遇到一个关于模板的问题,有一定记录价值。需求如下:
12345template<class T>void mySort(T* vec, int len);template<class function>void sortTester(function fun);
其中 mySort 是我自己编写的排序函数,vec 是数组,len 是数组长度;sortTester 是对数器,用来生成随机样例来检测传入的排序函数,参数 fun 为我们要传入并检测的函数。
当我想当然地进行如下调用时,编译器报错:
1sortTester(mySort);
这是很容易犯的一种错误,其实稍加思索后就能发现问题所在: sortTester 的参数类型 function 是一个模板类型,而传入的参数 mySort 是一个函数模板,所以 sortTester 当然就无法进行函数类型推断,因而报错。所以我们必须传入一个确定的函数类型,而非函数模板,即,将函数模板实例化。
有下面两种方式将函数模板实例化(这里实例化为 int ):
123//方式1 auto funcPtr = myS ...