kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Makefile与Shell的问题

大概只要知 道Makefile的人,都知道Makefile可以调用Shell脚本。但是在实际使用时,并不那么简单,一些模棱两可的地方可能会让你抓狂。你若不信,可以先看几个例子,想象一下这些这些例子会打印什么内容,记下你想象的结果,然后在计算机上运行这些例子,对照看一下。

示例一:

1
2
3
4
5
6
7
if [ "$(BUILD)" = "debug" ]; then
	echo "build debug"; 
else
	echo "build release";
fi
all:
	echo "done"

示例二:

1
2
3
all:
	@CC=arm-linux-gcc
	@echo $(CC)

示例三:

1
2
3
CC=arm-linux-gcc
all:
	@echo $(CC)

示例四:

1
2
3
4
5
6
SUBDIR=src example
all:
	@for subdir in $(SUBDIR);
	do
		echo "building " $(subdir);
	done

说明:

1.Shell脚本在target里才有效,其它地方都被忽略掉了。所以示例一中,”build debug”之类的字符串根本打印不出来。示例一的正确写法是: 示例一:

1
2
3
4
5
6
7
all:
	if [ "$(BUILD)" = "debug" ]; then
		echo "build debug";
	else
		echo "build release";
	fi
	echo "done"

2.make把每一行Shell脚本当作一个独立的单元,它们在单独的进程中运行。示例二中,两行Shell脚本在两个莫不相干的进程里运行,第一个进程把 CC设置为arm-linux-gcc,第二个进程是不知道的,所以打印的结果自然不是arm-linux-gcc了。示例二的正确写法是:
示例二:

1
2
3
4
5
6
all:
	@CC=arm-linux-gcc; echo $(CC)
或者:
all:
	@CC=arm-linux-gcc;
	echo $(CC)

3.make在调用Shell之前先进行预处理,即展开所有Makefile的变量和函数。这些变量和函数都以$开头。示例三中,Shell拿的脚本实际上是echo arm-linux-gcc,所以打印结果正确。

4.make预处理时,所有以$开头的,它都不会放过。要想引用Shell自己的变量,应该以$$开头。另外要注意,Shell自己的变量是不需要括号的。示例四的正确写法是:
示例四:

1
2
3
4
5
6
SUBDIR=src example
all:
	@for subdir in $(SUBDIR);
	do
		echo "building " $$subdir;
	done

字节序和比特序

字节序和比特序,因为比特序对所有代码(包括汇编)是透明的,所以对于小端系统,有说是用大端比特序,也有说是用小端比特序。

下面是copy一部分觉得靠谱的内容:

大小端

我们对"endianness"这个名词估计都很熟悉了。它首先被Danny Cohen于1980引入,用来表述计算机系统表示多字节整数的方式。

endianness分为两种:大端和小端。(从字节序的角度来看)大端方式是将整数中最高位byte存放在最低地址中。而小端方式则相反,将整数中的最高位byte存放在最高地址中。

对于某个确定的计算机系统,比特序通常与字节序保持一致。

换言之,在大端系统中,每个byte中最高位bit存放在内存最低位;在小端系统中,最低位bit存放在内存最低位。

正如大部分人是按照从左至右的顺序书写数字,一个多字节整数的内存布局也应该遵循同样的方式,即从左至右为数值的最高位至最低位。正如我们在下面的例子中所看到的,这是书写整数最清晰的方式。

根据上述规则,我们按以下方式分别在大端和小端系统中值为0x0a0b0c0d的整数。

在大端系统中书写整数:

1
2
3
4
5
byte  addr   0   1   2   3
bit offset  01234567 01234567 01234567 01234567

    binary  00001010 00001011 00001100 00001101
      hex      0a       0b       0c       0d

在小端系统中书写整数(认真看)

1
2
3
4
5
byte  addr   0   1   2   3
bit offset  01234567 01234567 01234567 01234567

    binary  10110000 00110000 11010000 01010000
      hex      d0       c0       b0       a0

说明字节序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main (void)
{
	union b {
		short k;  //测试环境short占2字节
		char i[2];  //测试环境char占1字节
	} *s, a;

	s = &a;
	s->i[0] = 0x41;
	s->i[1] = 0x52;
	printf("%x\n", s->k);
	return 0;
}

输出:5241


self code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
union W
{
	struct Y {
		unsigned int s1:4;
		unsigned int s2:8;
		unsigned int s3:20;
	} y;
	unsigned int c;
} w;

union V 
{
	struct X {
		unsigned char s1:3;
		unsigned char s2:3;
		unsigned char s3:2;
	} x;
	unsigned char c;
} v;

int main()
{
	w.c = 0x12345678;
	printf("%x <==> %x\t%x\t%x\n", w.c, w.y.s1, w.y.s2, w.y.s3);

	v.c = 100;
	printf("%d\t <==> %x\t%x\t%x\n", v.c, v.x.s1, v.x.s2, v.x.s3);
	return 0;
}

输出:

1
2
12345678 <==> 8    67  12345
100    <==> 4   4   1

100 = (01100100)2

因为字节序是小端的所以第一行输出说明:位域变量从左到右分配位,所以第二行的输出的位域变量也应该从左到右分配位。所以

1
2
100 = 001 001 10  (小端比特序二进制)
对应:  s1  s2  s3  (位域变量从左到右分配位)

符合。

jmp指令对应的机器码

短跳转和近跳转指令中包含的操作数都是相对于(E)IP的偏移,而远跳转指令中包含的是目标的绝对地址,所以短/近跳转会出现跳至同一目标的指令机器码不同,不仅会不同,而且应该不同。而远跳转中包含的是绝对地址,因此转移到同一地址的指令机器码相同

绝对跳转/调用指令中的内存操作数必须以’*’为前缀,否则gas总是认为是相对跳转/调用指令,而且gas汇编程序自动对跳转指令进行优化,总是使用尽可能小的跳转偏移量。如果8比特的偏移量无法满足要求的话,as会使用一个32位的偏移量,as汇编程序暂时还不支持16位的跳转偏移量,所以对跳转指令使用’addr16’前缀是无效的。还有一些跳转指令只支持8位的跳转偏移量,这些指令是:’jcxz’,’jecxz’,’loop’,’loopz’,’loope’,’loopnz’’loopne’如果你在汇编中使用了这些指令,用gas的汇编可能会出错,因为gcc在编译过程中不产生这些指令,所以在c语言中不必担心这些问题。

1
2
ffffffff88873036      e8 ff ff 5f c6   =>  call XX        // e8 = call
ffffffff8887303a      ......

相当于:目标地址 - ffffffff8887303a(RIP, 指向下一条指令) = ffffffffffff5fc6 (这个是负数,以补码形式展示)
所以: 目标地址 = ffffffff88869000

  • 即: ffffffff88869000 - ffffffff8887303a + ffffffffffffffff + 1 = ffffffffffff5fc6
  • 可以用 unsigned long 类型来计算,让它自然溢出就好了,(unsigned long)func1 - ((unsigned long)func2 + 0x偏移)

先计算好偏移,再替换call地址,就偷偷的改了调用。

附:若为e8 00 00 00 00 则可以同过模块读取 00 00 00 00 的实际值