Rolling Stern

  • Home

  • Tags

  • Archives

风口优化理论

Posted on 2020-04-02

可及度

可及度的计算

可及度(accessibility),即送风口对室内指定空间位置点的影响力,记作 $ASA_i(x,y,z,\tau)$ ,可由下式计算得到:

$$ASA_i(x,y,z)={ {\int_0^\tau C(x,y,z) dt} \over {C_{s,i}\cdot \tau} }$$

符号 意义
$x,y,z$ 点的空间三维座标
$\tau$ 经过的时间(s)
$C$ 浓度($mg/m^3$)
$s,i$ 第 i 个 source(送风口)

可及度反解定点浓度

某点在 $\tau$ 时刻的浓度,可以由下式计算得出
$$ \overline C(x,y,z,\tau)=C_0 + \sum_{i=1}^M(C_{s,i}-C_0)\cdot ASA_i(x,y,z,\tau) $$

可及度文件的格式

第 i 个送风口对房间内各个点的可及度数据格式如下表所示:

点 1 点 2 点 3 … 点 n
0 时刻 … … … … …
1 时刻 … … … … …
… … … … … …
t 时刻 … … … … …

示例文件中共有9个点,196个时刻,相邻两个时刻间隔9秒。

暴露量

根据 Haber 定律,污染物对人的危害程度,与其在环境中的暴露量E(Exposure)有关,暴露量可根据下式计算得到:

$$E(x,y,z,\tau)=\int_0^\tau C(x,y,z,\tau)dt$$

IR(Injury Risk)

在一定毒理环境下,人可能会存在受伤的可能性,这种可能性(0~1)定义为IR(Injury Risk)
$$IR=P={1\over(2\pi)^{1/2}}\times\int_{-\infty}^{Y-5}{\exp(-{u^2\over2})du}$$

可见,$P$ 为一正太变量的分布函数,记作$Y-N(5,1)$。当$Y-5=0$时,概率最高,积分增长最快。同时,若$Y$越大,则死亡可能性越高,当$Y-5=\infty$ 时,死亡的可能性为 1

因此,$Y$的计算就显得额外重要。理论上$Y$应该用如下方程计算:
$$Y=a+b \ln(V)$$
其中,a和b都常数,V是脆弱性指数。由方程可见,V越高,则死亡率P越高。为了评估毒性的作用,脆弱性指数V应该与暴露量E有关,则上面的方程可改写为:
$$Y=a+b\ln(E(x,y,z,\tau))$$

此时,又有一个问题摆在了我们面前,a和b如何确定?这里a和b应该是与化学品的性质有关,例如A物质在1mg/m3的足以致死,而物质B在1mg/m3时对人毫无影响。

有种方法可以通过有害物的危险等级(Acute Exposure Guideline Levels, AEGLs)反解出参数a和b。AEGLs(单位为浓度)分为三个等级,分别是AEGL-1、AEGL-2、AEGL-3:

  • 浓度在 AEGL-1 以下:不会感到异常
  • 浓度达到 AEGL-2 :开始对大部分人造成影响
  • 浓度超过 AEGL-3 :对生命造成威胁

在一定的暴露时间 t 内,(通常查文档可)查到其暴露浓度 AEGL-2 和 AEGL-3 下的受伤比例,记为 L2 和 L3。由此,可反推参数a和b如下所示:
$$L_2 = {1\over{2\pi}^{1/2}} \times\int_{-\infty}^{Y_2-5} \exp({-{u^2}\over 2})$$
其中:
$$Y_2=a+b\ln(\tau\times AEGL-2)$$

OD(Occupied Density)

通常来说,人并不会只站在同一点,也不会占据空间所有点。统计指标OD被定义为描述整个时期内人员在特定区域内停留的时间比率(%)

假设房间被分割成了N块不同的区域,分别被标记为 $Zone_i$ ($i\in(1,N)$),人们在每个区域的总停留时间为 $T_i$,而 $\tau$ 则被定义为人们在整个房间内停留的时间,由此 OD 可被定义为:
$$OD(Zone_i,\tau)={T_i\over\tau}$$
其中
$$\sum_{i=1}^{N}T_i = \tau$$

然而,我们前文建立的模型是采用三维座标的形式确定空间位置,所以上述方程可改写为ODD(Occupied Distribution Density)格式:
$$\iiint_{zone}ODD(x,y,z,\tau)dxdydz=OD(Zone,\tau)$$

这样一来,在对空间各点的IR和ODD有了定量分析后,在房间内停留 $\tau$ 秒内,人受伤的可能性(PIR)为:
$$PIR(\tau)=\iiint_{V_{room}}IR(x,y,z,\tau)\times ODD(x,y,z,\tau)dxdydz$$

如果$PIR=0$则意味着没有任何影响,$PIR=L_2$时则标志着会对人产生可见的影响,而$PIR=L_3$时则意味着在其中的人有生命危险

优化模型

在描述优化模型前,首先定义两个下标,分别是下标h和t,分别代表hostages(人质)和terrorists(恐怖分子)

优化模型A

在保证对恐怖分子的有效杀伤下,尽可能降低人质所受到的伤害
$$
\begin{cases}
min&PIR_h(\tau)\
s.t.&PIR_t(\tau)\ge L_2, &i=1,…,M\
&0\leq C_{s,i}\leq C_{s,max}
\end{cases}
$$

优化模型B

在保证人质的绝对安全下,尽可能提高对恐怖分子的杀伤
$$
\begin{cases}
max&PIR_t(\tau)\
s.t.&PIR_h(\tau) < L_2, &i=1,…,M\
&0\leq C_{s,i}\leq C_{s,max}
\end{cases}
$$

优化模型C

在保证人质的安全和对恐怖分子的有效杀伤时,尽可能提高两者吸入气体的浓度差
$$
\begin{cases}
max&\Delta PIR(\tau)\
s.t.&PIR_h(\tau)\leq L_2 \
&PIR_t(\tau) \geq L_2 , &i=1,…,M\
&0\leq C_{s,i}\leq C_{s,max}
\end{cases}
$$
其中 $\Delta PIR(\tau) = PIR_t(\tau) - PIR_h(\tau)$

算法简介

基于上述模型,优化过程可以简化为一个规划问题,简化为一个常规的非线性问题的求解:

$$
\begin{cases}
min & f(x), &x \in R^n\
s.t. & h_i(x) = 0,& i=1,2,…,m\
& g_j(x) \leq 0, &j=1,2,…,m
\end{cases}
$$
其中 $f(x)$ 是需要优化的目标函数,采用scipy的优化工具可以简单地对这类问题进行优化计算。不需要额外处理。

茶炊逻辑

Posted on 2020-01-18

弗洛伊德谈到梦、玩笑和无意识时,称有人为了狡辩,会寻出一些稀奇古怪的理由来。比方说:一、我从未借过你的茶炊。二、我还你茶炊时完好无损。三、你借给我茶炊的时候炊底便有了这些窟窿。

关于 ELF 可执行文件的 Reloction 的笔记 2/2

Posted on 2019-08-25

装载时重定位的实际运作

通过创建一个简单的动态链接库载入程序,这样一来可以观察装载时重定位的实际的运作过程。但值得注意的是,由于Linux启用了地址空间配置随机加载(Address space layout randomization, ASLR)技术,从而导致重定位相对难以观察,因为每当我们运行程序时,libmlreloc.so 都会被载入到不同的内存空间(Virtual Address)中。

但是不必担心,即便如此我们也有方法能够观测到重定位过程。但首先,让我们谈一谈动态链接库中包含的段:

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
readelf --segments libmlreloc.so 

Elf file type is DYN (Shared object file)
Entry point 0x1030
There are 9 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00000000 0x00000000 0x00304 0x00304 R 0x1000
LOAD 0x001000 0x00001000 0x00001000 0x00168 0x00168 R E 0x1000
LOAD 0x002000 0x00002000 0x00002000 0x00050 0x00050 R 0x1000
LOAD 0x002f20 0x00003f20 0x00003f20 0x000f4 0x000f8 RW 0x1000
DYNAMIC 0x002f28 0x00003f28 0x00003f28 0x000c8 0x000c8 RW 0x4
NOTE 0x000154 0x00000154 0x00000154 0x00024 0x00024 R 0x4
GNU_EH_FRAME 0x002000 0x00002000 0x00002000 0x00014 0x00014 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x002f20 0x00003f20 0x00003f20 0x000e0 0x000e0 R 0x1

Section to Segment mapping:
Segment Sections...
00 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn
01 .init .text .fini
02 .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got

于前文中提到, myglob 存储于 .data 段中,在 Section to Segment mapping 中发现 .data 被映射在 03 段中,可以发现 03 段 虚拟地址起始于 0x3f20,其内存中的大小为 0xf8 ,这意味着 03 段是从 0x3f20 开始, 到 0x4018 结束,正好包含 myglob ( myglob 位于 0x4010 )。

现在,让我们用一个 Linux 给我们的便利工具, dl_iterate_phdr 函数, 来检验载入时的过程。这个函数允许我们的应用程序获取实际载入的动态链接库。

所以,让我们将如下代码保存到 driver.c 中

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
31
#define _GNU_SOURCE
#include <link.h>
#include <stdlib.h>
#include <stdio.h>


static int header_handler(struct dl_phdr_info* info, size_t size, void* data)
{
printf("name=%s (%d segments) address=%p\n",
info->dlpi_name, info->dlpi_phnum, (void*)info->dlpi_addr);
for (int j = 0; j < info->dlpi_phnum; j++) {
printf("\t\t header %2d: address=%10p\n", j,
(void*) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr));
printf("\t\t\t type=%u, flags=0x%X\n",
info->dlpi_phdr[j].p_type, info->dlpi_phdr[j].p_flags);
}
printf("\n");
return 0;
}


extern int ml_func(int, int);


int main(int argc, const char* argv[])
{
dl_iterate_phdr(header_handler, NULL);

int t = ml_func(argc, argc);
return t;
}

其中 header_handler 函数是供给 dl_iterate_phdr 回调用,它会在所有动态链接库都报告其名字、装载的实际地址和其所有的段地址后被调用。 在此之后将会执行 ml_func ,该函数则是从 libmlreloc.so 中导出的。

编译该程序并链接动态链接库,需要如下命令:

1
2
$ gcc -m32 -g -c driver.c -o driver.o -fno-pic
$ gcc -m32 -o driver driver.o -L. -lmlreloc -fno-pic

为了使程序能够正常载入当前目录下的动态链接库,还需要一条额外的命令:

1
export LD_LIBRARY_PATH=./

该命令告诉系统查找动态连接库时,还应当在程序所在的目录查找。该设置退出当前终端后失效,所以不会污染环境。

运行 driver 后我们就能得到载入到 driver 程序中的所有动态链接库的信息。可是每次我们得到的信息都是不同的,所以我们需要将其用 gdb 调试,看看程序输出,同时用 gdb 获取相应内存空间的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
gdb -q driver
Reading symbols from driver...
(gdb) b driver.c:31
Breakpoint 1 at 0x12c4: file driver.c, line 31.
(gdb) r
Starting program: /home/tricks/Documents/Projects/test/driver
[...] 省略部分输出
name = ./libmlreloc.so (9 segments) address = 0xf7fc8000
header 0: address = 0xf7fc8000
type = 1, flags = 0x4
header 1: address = 0xf7fc9000
type = 1, flags = 0x5
header 2: address = 0xf7fca000
type = 1, flags = 0x4
header 3: address = 0xf7fcbf20
type = 1, flags = 0x6
[...] 省略部分输出
Breakpoint 1, main (argc=1, argv=0xffffc6e4) at driver.c:31
31 }
(gdb)

程序在断点暂停后,我们观察其输出会发现, libmlreloc.so 有9个段,这和之前 readelf 程序报告的数量相同,同时这次不同点在于该库已经被实际载入一个确定的内存空间中了。

让我们做一些计算,包含有 myglob 的 03 段,该段被载入到了 0xf7fca000 处。回顾本文前半段 readelf 的输出「 myglob 位于 0x4010 处, 03 段的起始地址为 0x3f20 」,则 myglob 相对于 03 段段首的偏移量为 0x4010 - 0x 3f20 = 0xf0 , 再将这个偏移量加上 03 段实际载入的内存地址 0xf7fcbf20 , 则计算出 myglob 在载入后的实际内存地址为 0xf7fcbf20 + 0xf0 = 0xf7fcc010 。

让我们在 gdb 中验证下这个计算是否正确:

1
2
(gdb) p &myglob
$1 = (int *) 0xf7fcc010 <myglob>

完美!同时另一个疑惑又来了,在库函数 ml_func 中是如何访问 myglob 的呢?不妨再一次问问 gdb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb) set disassembly-flavor intel
(gdb) disas ml_func
Dump of assembler code for function ml_func:
0xf7fc912d <+0>: push ebp
0xf7fc912e <+1>: mov ebp,esp
0xf7fc9130 <+3>: mov edx,DWORD PTR ds:0xf7fcc010
0xf7fc9136 <+9>: mov eax,DWORD PTR [ebp+0x8]
0xf7fc9139 <+12>: add eax,edx
0xf7fc913b <+14>: mov ds:0xf7fcc010,eax
0xf7fc9140 <+19>: mov edx,DWORD PTR ds:0xf7fcc010
0xf7fc9146 <+25>: mov eax,DWORD PTR [ebp+0xc]
0xf7fc9149 <+28>: add eax,edx
0xf7fc914b <+30>: pop ebp
0xf7fc914c <+31>: ret
End of assembler dump.

正如我们所预料的, myglob 的真实地址被写入了 MOV 指令的操作数。这样一来,对于内存操作数,装载时重定位机制的运作就非常明晰了。

装载时重定位 —— 对于函数而言

有了前面的基础,后面函数的重定位就显得比较简单了。在 objdump 的输出中找到未载入内存时的函数调用指令:

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
$ objdump -d -Mintel driver

[...] 忽略部分输出

000011cd <header_handler>:
11cd: 55 push ebp
11ce: 89 e5 mov ebp,esp
[...] 忽略部分输出

00001289 <main>:
1289: 8d 4c 24 04 lea ecx,[esp+0x4]
128d: 83 e4 f0 and esp,0xfffffff0
1290: ff 71 fc push DWORD PTR [ecx-0x4]
1293: 55 push ebp
1294: 89 e5 mov ebp,esp
1296: 53 push ebx
1297: 51 push ecx
1298: 83 ec 10 sub esp,0x10
129b: 89 cb mov ebx,ecx
129d: 83 ec 08 sub esp,0x8
12a0: 6a 00 push 0x0
12a2: 68 cd 11 00 00 push 0x11cd
12a7: e8 fc ff ff ff call 12a8 <main+0x1f>
12ac: 83 c4 10 add esp,0x10
12af: 83 ec 08 sub esp,0x8
12b2: ff 33 push DWORD PTR [ebx]
12b4: ff 33 push DWORD PTR [ebx]
12b6: e8 fc ff ff ff call 12b7 <main+0x2e>
12bb: 83 c4 10 add esp,0x10

动态连接库提供的函数

首先从动态链接库提供的函数 ml_func 开始:

对比源文件,很容易就能认出调用函数 ml_func 的指令在 RVA 的 0x12b6 处。 e8 是 CALL 指令的 OPCODE ,所以CALL的地址为 fc ff ff ff ,即 0xfffffffc,或者说 -4 。

确认 .reloc 表的内容:

1
2
3
4
5
6
7
readelf -r driver                       

Relocation section '.rel.dyn' at offset 0x3ec contains 18 entries:
Offset Info Type Sym.Value Sym. Name
[...] 忽略部分输出
000012b7 00000402 R_386_PC32 00000000 ml_func
[...] 忽略部分输出

这次的 Offset 为 0x12b7 和我们预想的一样,可是 Type 却为 R_386_PC32 而非之前的 R_386_32 ,同时 Sym.Value 为 0 。很明显,这应该比之前的要难上一点,不过也就仅仅只难一点而已。简单地说就是:于 .rel.dyn (即动态链接库提供函数的)表中的 Offset 值, Offset + Base 即为需要替换的量的内存地址(记为 VA_SOURCE );在动态链接库载入后,根据 Sym.Name 确认的实际地址(记为 $VA_{DEST}$ ),相对位置则为 $VA_{DEST} - VA_{SOURCE}$ 。因为 CALL 指令执行时 EIP 并不位于 $VA_{SOURCE}$ ,而是下一条指令处。下一条指令和 $VA_{SOURCE}$ 的位置差为 4 (这就是之前 0xfffffffc 的来源),则实际运算公式为:

$$ REPLACEMENT = VA_{DEST} - VA_{SOURCE} - 4 $$

局部函数

对于局部函数,问题就更简单了,观察 RVA 0x12a2 处,那是我们传入的回调函数的地址,其内容为 0x11cd ,是不是很眼熟?这就是 header_handler 的 RVA 地址呀!

让我们再确认下 reloc 表中的记录:

1
2
3
4
5
6
7
readelf -r driver                       

Relocation section '.rel.dyn' at offset 0x3ec contains 18 entries:
Offset Info Type Sym.Value Sym. Name
[...] 忽略部分输出
000012a3 00000008 R_386_RELATIVE
[...] 忽略部分输出

没有 Sym.Value ,没有 Sym.Name , 只有 Offset 和 Type, Offset 指出了何处需要修改,其 Type 为 R_386_RELATIV ,其意思就是,若基址变换了,基址增加1,这个值也相应增加1就可以了。

结语:

关于 Linux 上 ELF 文件的重定位就写到这里了,言不达意,翻译起来痛恨自己中文水平太低。若有错误,欢迎指正。

关于 ELF 可执行文件的 Reloction 的笔记 1/2

Posted on 2019-08-24 | Edited on 2020-01-18

这是关于 .reloc 分段的一系列文章的第一章。本文主要内容翻译自 Eli Bendersky 的文章 Load-time relocation of shared libraries。本文的实验平台为 x86_64 GNU/Linux,编译器则采用的是 gcc 9.1.0。

Relocs 和 内存空间

程序在运行前,首先会被加载进入到一定的内存空间,PE、ELF头则指定了程序各个段应该载入内存的何处。只有各个段被载入到相应的内存地址,样程序才能正常寻址。因此程序在编译过程中,编译器将为各个段设定起始地址,所有寻址都以这个地址为基准。

现代操作系统,为了使系统更加安全稳定,采用了一项名为 装载时重定位(Load-time relocation) 的技术,在 windows 中又被称为 基址重置(Rebasing)技术,即忽略PE、ELF头指定的基址,随机选择一个内存区间载入程序。该方法会导致程序的内存地址不确定,使得恶意代码在注入后无法正常工作,同时“正常”程序也无法使用编译时“假设” 的基址寻址。所以需要在载入时根据实际分配的随机的基址,更改(fix up)写在二进制可执行文件中的操作数地址,使得程序在被映射到随机的内存空间后仍能正常执行。因此 PE、ELF 可执行文件将带有一个称为 relocs 的表,这个表将记录这些修改(fix up)。

载入动态链接库(Shared libraries)

在实际情况中,对于可执行文件 .reloc 并不是必须的,这意味着删除 .reloc 后程序仍然能够正常装载运行。这是因为可执行文件总是第一个载入内存空间(Virtual Memory)中。可是对于动态链接库就没有这么幸运了,它随着程序的载入一起载入的,同一个动态链接库要为不同程序服务,各个程序使用的内存地址各不相同,所以动态连接库无法保证自己永远被载入到某一固定的内存地址。因此必须要使用 装载时重定位 技术,动态链接库才能正常工作。

现今,有两种主流的方法来提供动态装载时内存地址不确定的问题:

  1. 装载时重定位 (Load-time relocation)
  2. 位置无关代码 (Position independent code, PIC)

即使PIC是现今更推荐的解决方法,甚至在64位Linux平台上PIC已经成为动态链接库的默认选项,但是本文还是着重于 装载时重定位(Load-time relocation) 方法。

注意:本文着重点在 Linux 平台上的关于 装载时重定位 技术的内容,我本人使用的系统为 x86_64 GNU/Linux,编译器采用的 gcc 9.1.0 ,情多注意。

首先,让我们做一个简单的实验,将下列 C 代码保存为 ml_main.c

1
2
3
4
5
6
7
int myglob = 42;

int ml_func(int a, int b)
{
myglob += a;
return b + myglob;
}

记住,因为动态链接库不知道自己将会被载入何处,所以自然无法在编译时确定 myglob 变量的实际内存地址,该地址应该是动态链接库被载入内存时确定。

将其编译为32位动态连接库

1
2
$ gcc -m32 -g -c ml_main.c -o ml_mainreloc.o -fno-pic
$ gcc -m32 -shared -o libmlreloc.so ml_mainreloc.o -fno-pic

首先,确认下该动态连接库的入口地址:

1
2
3
4
5
6
7
$ readelf -h libmlreloc.so
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
[...] 略过
Entry point address: 0x1030
[...] 略过

该动态链接库的入口地址为 0x1030 ,对于Linux上的动态链接库,其入口地址即为 .text 段的首地址,这说明编译器(链接器)假设该动态链接库的 .text 段被载入到内存 0x1030 处。但我们知道,该动态链接库绝不可能载入到该内存区域。

我们看一看该动态链接库的反汇编:

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
$ objdump -d -Mintel libmlreloc.so
libmlreloc.so: file format elf32-i386

[...] 略过代码

Disassembly of section .text:

00001030 <__x86.get_pc_thunk.bx>:
1030: 8b 1c 24 mov ebx,DWORD PTR [esp]
1033: c3 ret

[...] 略过代码

0000112d <ml_func>:
112d: 55 push ebp
112e: 89 e5 mov ebp,esp
1130: 8b 15 00 00 00 00 mov edx,DWORD PTR ds:0x0
1136: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
1139: 01 d0 add eax,edx
113b: a3 00 00 00 00 mov ds:0x0,eax
1140: 8b 15 00 00 00 00 mov edx,DWORD PTR ds:0x0
1146: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
1149: 01 d0 add eax,edx
114b: 5d pop ebp
114c: c3 ret

[...] 略过代码

可见 .text 段确实被假设在 0x1030 处, .text 段的第一个函数为 __x86.get_pc_thunk.bx ,而我们的函数 ml_func 则在 0x112d` 处。

让我们重点关注 ml_func 函数,观察它是如何完成 myglob += a 的。0x1136 处从 EBP+0x8 中取值到 EAX 我们可以很容易推断出这就是传入参数 a ,同时因为 0x1139 处 EAX 和 EDX 相加,那么 myglob 的值被存在 EDX 中。再往上看,发现 MOV edx, DWORD PTR ds:0x0 这段代码,那么 ds:0x0 则为全局变量 myglob 所在处了。

但是等等,为什么是 ds:0x0 ? (现代操作系统早就不再象DOS系统一样,利用段寄存器来划分内存段了,同时,仔细分析objdump的输出我们会发现程序被载入到 0x1000 处)0x0 处根本就不是程序可以正常利用的空间,但是 MOV 指令确实从这里取值了。这时我们应该反应过来,装载时重定位应当在这里发挥作用, 0x0 应当是类似于C语言中 #DEFINE 一样的存在,在载入时被替换为实际可用的,指向 myglob 变量的内存地址。让我们确认下:

1
2
3
4
5
6
7
8
9
$ readelf -r libmlreloc.so

Relocation section '.rel.dyn' at offset 0x2b4 contains 10 entries:
Offset Info Type Sym.Value Sym. Name
[...] 省略部分输出
00001132 00000501 R_386_32 00004010 myglob
0000113c 00000501 R_386_32 00004010 myglob
00001142 00000501 R_386_32 00004010 myglob
[...] 省略部分输出

rel.dyn 是ELF中为装载时重定位提供信息的分段。一共有三条关于 myglob 的记录,这意味着 myglob 在程序中有三次引用,让我们翻译下第一条记录:

在偏移量为 0x1132 处,对一个类型为 R_386_32 的记录进行重定位操作。

我们很容易发现 R_386_32 指的是32位数据,表示该记录从 0x1132 开始,是一个四个字节长的记录。我们返回去看便宜量为 1132 处的指令,发现它在 ml_func 内,该指令为:

1
1130:	8b 15 00 00 00 00    	mov    edx,DWORD PTR ds:0x0

1130 到 1131 处为 8b 15,应该是 MOV 指令和第一个操作数 edx,而 1132 到 1135 处为 00 00 00 00 指令,即 ds:0x0。

这条记录即说:在载入时,将 0x1132 处的 myglob 变量的 32 位地址更换为实际的地址。是不是很简单呢?

值得注意的是,在 .reloc 段的 Offset 列,即记录将要被替换掉的记录的地址,这个地址是 myglob 在内存地址(Virtual Memory)中的地址。如之前提到过,编译链接过程中,编译器(链接器)假设动态链接库的 .text 段被加载到内存地址(Virtual Memory)的0x1000处。所以 Sym.value 中的地址并非文件偏移量,而是“基于假设基地址的偏移量”,或者更准确说是相对虚拟地址(Relative Virtual Address, RVA)。

同样应该注意的还有 Sym.Value 列, 0x4010 指的是变量 myglob 在 RVA 中的位置,如前文所提到的,程序镜像被载入到地址为 0x0 的位置, .text 段被载入到 0x1030 位置。 可以用如下命令更加直观地分析各个段的文件位置和RVA:

1
2
3
4
5
6
7
8
9
10
11
$ readelf -S libmlreloc.so 
There are 29 section headers, starting at offset 0x37e0:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[....] 省略部分输出
[ 9] .text PROGBITS 00001030 001030 00011d 00 AX 0 0 16
[....] 省略部分输出
[18] .data PROGBITS 0000400c 00300c 000008 00 WA 0 0 4
[....] 省略部分输出

该输出印证了之前的观点,那么 Sym.Value 中 0x4010 指的就很明确了,即回答 myglob 的实际地址该怎么计算的问题。这里 Addr 列即 RVA, Off 列即文件偏移地址。data 段被假设载入到 0x400c ,此时 myglob 则被载入到 0x4010 ,当 data 段被载入到一个实际的地址时, myglob 的实际地址则为 .data 的实际地址 + 0x4010 - .data 假设地址(0x400C),是不是豁然开朗了呢?

阅读的故事

Posted on 2019-03-25 | Edited on 2020-02-28

既然文字从未有过任何的自明性,其指向的乃为万千差异中的差异,而从不指明的话,那么文本也是一样无任何封闭的、绝对的意义。这样一来读者便成为了赋予文本意义的存在。正如沃尔夫冈・伊瑟尔所说的“读者积极填补空白,赋予文本游移不定处以明确意义,尝试建立一种统一性,并修正结构以使文本产生更多信息”。读者凌驾于文本之上,利用自身的经验来重新构建或产生角色,这便是广为流传的关于阅读的故事(The story of reading)。

若仅看上面的说法,仿佛文本与读者的关系已经明确且无需争辩,似乎是读者控制了文本,是读者积极填补空白,赋予文本游移不定处以明确意义,可实事真的如此么?若改变视角,阅读的故事却变成了“文本激发读者某种反应,从而主动控制了读者”的故事。阅读过程赋予文本以意义,构建了文本,但同时文本给予读者阅读的资料与方法,控制了阅读过程。事实上,读者与文本的故事绝非读者单方面凌驾于文本之上的故事,这样一来,读者与文本之间,阅读与文本之间的因果关系,或者说绝对的阶级关系,便被解构了。

读者与文本之间,我们有理由相信必须兼顾读者和文本两方,才能构建起阅读理论来。以网络黑话笑话为例来说明这个问题“笑话中听众是必须的,因为除非听众笑了,否则笑话便不成为其笑话(就如同文本不同于文本一样,失去意义的文本只是文字的堆砌),这里便显示了读者在决定话语的意义中扮演了决定性的角色。同时听众并不控制笑的爆发,是文本激发了它,诚如人们所说,是这笑话让我发笑。”若仅此解释,则在上段的矛盾上并无发展。网络笑话之所以成为笑话,和网络黑话密切相关————“是以网络黑话的经验为前提,没有黑话的阅读,就没有更进一步的网络黑话笑话的经验,网络黑话笑话的经验反过来进一步激发了更多的网络黑话笑话的阅读”。所以说,阅读的故事则是经验欣赏经验的故事,读者的在阅读中提供的经验,系文本所提供。且同时,文本在实际上也控制了读者的阅读逻辑————读者之所以成为读者的,引以为傲的思维逻辑,其实也是文本所构建的。至此,我们可以引出结论,阅读的故事,实际上是由某种是文本又非文本,是经验又非经验的某种东西,其自我欣赏自我创造的故事,其中既没有读者的位置,也没有文本的位置,文本所给出(结构或者内容)的与读者所给出的(经验或者内涵)之间的看似绝对的分界线,便瞬间土崩瓦解了,读者–文本的二元论便坍塌成了一种一元论。

作为女性的阅读与批评

Posted on 2019-03-21 | Edited on 2019-03-22

每当我们自认为自己的评判公正无所偏颇时,我们却往往忽视了一种以女性视角审视文本的批评角度。若要“以女性视角审视文本”,需要的仅仅是读者是一名女性么?决定“以女性声音批评”的,是某种生理条件还是一种策略的,或称为理论的立场呢?是解剖学还是文化呢?

每当我们试图以女性的视角来阅读与评论时,总是发现其是一种矛盾的期待:它诉诸某种先决的条件——即作为女性的阅读经验————可这种阅读经验确是需要争取才能取得的。通俗的说即是:我们需要拥有女性的阅读经验,才能进行以女性的视角来阅读与批评,但是女性通常难以作为女性来阅读,她们对文本的理解,往往倚仗的是一种男性的阅读经验,在漫长的父权社会历史背景下,这种男性的阅读经验业已成为全人类的经验。女性所用的符号系统,也是旧时代流传下来的,说到底,女性可能从未拥有过她们自己的语言,也就更谈不上阅读经验了。同时,我们还迷惑于女性并没有本质上不同于男性的特征,你能说男性与女性的头脑构造上有什么本质不同么?于是,我们争取女性的阅读经验需要的是“以女性的角度审视文本”,但问题是没有女性阅读经验作为背景,我们何谈“以女性的视角审视文本”呢?最终,我们仅知的只有“女性读者的前提,改变我们对待特定文本的理解,提醒我们注意其中有关性代码的含义”。

所以,以女性角度审视文字时,应是“成为一个抵御抗拒而非一味点头的读者,通过对赞成顺从的这一行为的抵制,开始拔除植入我们中间的男性意向的过程”。我们可以仅能在纯粹差异的角度,下一个定义,即“作为一个女性来阅读,是避免作为男性来阅读,是识别男性阅读的具体辩护及其变形,并加以批驳纠正”。

至此,其本身是否为女性就显得无关紧要了。

阅读中的精神活动

Posted on 2019-03-20 | Edited on 2019-03-21

阅读中的精神活动分为三种

  1. 以某种方式运思
  2. 评判活动的实施和追悔
  3. 逻辑线索的追踪与形成

SternW Zhang

The blog site of SternW Zhang
7 posts
7 tags
RSS
GitHub E-Mail
Creative Commons
© 2020 SternW Zhang
Powered by Hexo v3.9.0
|
Theme – NexT.Muse v7.0.1