计算机系统篇之链接(12):Chapter 7 Linking 习题与解答
Author: stormQ
Created: Monday, 15. July 2019 11:18AM
Last Modified: Thursday, 05. November 2020 11:03PM
swap.c:
extern int buf[];
int *bufp0 = &buf[0];
static int *bufp1;
static void incr()
{
static int count = 0;
count++;
}
void swap()
{
int temp;
incr();
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
生成可重定位目标文件 swap.o:
$ gcc -c swap.c
查看 swap.o 的符号表:
$ readelf -s swap.o
Symbol table '.symtab' contains 14 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS swap.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 5
5: 0000000000000000 8 OBJECT LOCAL DEFAULT 5 bufp1
6: 0000000000000000 22 FUNC LOCAL DEFAULT 1 incr
7: 0000000000000008 4 OBJECT LOCAL DEFAULT 5 count.1760
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 bufp0
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND buf
13: 0000000000000016 74 FUNC GLOBAL DEFAULT 1 swap
查看 swap.o 的表头:
$ readelf -S swap.o
There are 13 section headers, starting at offset 0x420:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000060 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002b8
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000a0
0000000000000008 0000000000000000 WA 0 0 8
[ 4] .rela.data RELA 0000000000000000 00000378
0000000000000018 0000000000000018 I 11 3 8
[ 5] .bss NOBITS 0000000000000000 000000a8
000000000000000c 0000000000000000 WA 0 0 8
[ 6] .comment PROGBITS 0000000000000000 000000a8
0000000000000033 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000db
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000e0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000390
0000000000000030 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 000003c0
000000000000005e 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000138
0000000000000150 0000000000000018 12 11 8
[12] .strtab STRTAB 0000000000000000 00000288
000000000000002d 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
因此,该题的解答为:
符号 | swap.symtab 条目? | 符号类型 | 定义符号的模块 | 节 |
---|---|---|---|---|
buf | yes | extern | m.o | .data |
bufp0 | yes | global | swap.o | .data |
bufp1 | yes | local | swap.o | .bss |
swap | yes | global | swap.o | .text |
temp | no | - | - | - |
incr | yes | local | swap.o | .text |
count | yes | local | swap.o | .bss |
a)先执行原来的代码
bar5.c:
double x;
void f()
{
x = -0.0;
}
foo5.c:
#include <stdio.h>
void f();
int x = 15213;
int y = 15212;
int main()
{
f();
printf("x = 0x%x(%d) y = 0x%x(%d)\n", x, x, y, y);
return 0;
}
注意:这里调整了书中变量x
和y
的定义顺序。因为在我们的系统中(X86-64 Linux, Little-endian)中,按照上述定义顺序时,x
的地址是 0x601038,y
的地址是 0x60103c。这一点可以通过下文查看可执行目标文件的符号表得到验证。
生成可执行目标文件——foo5:
$ gcc -o foo5 foo5.c bar5.c
/usr/bin/x86_64-linux-gnu-ld: Warning: alignment 4 of symbol `x' in /tmp/ccCUovlG.o is smaller than 8 in /tmp/ccNJpz7k.o
注:gcc 版本为 6.5。
运行 foo5:
$ ./foo5
x = 0x0(0) y = 0x80000000(-2147483648)
查看 foo5 的符号表:
$ readelf -s foo5
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 71 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002b8 0 SECTION LOCAL DEFAULT 5
6: 0000000000400318 0 SECTION LOCAL DEFAULT 6
7: 0000000000400358 0 SECTION LOCAL DEFAULT 7
8: 0000000000400360 0 SECTION LOCAL DEFAULT 8
9: 0000000000400380 0 SECTION LOCAL DEFAULT 9
10: 0000000000400398 0 SECTION LOCAL DEFAULT 10
11: 00000000004003c8 0 SECTION LOCAL DEFAULT 11
12: 00000000004003f0 0 SECTION LOCAL DEFAULT 12
13: 0000000000400420 0 SECTION LOCAL DEFAULT 13
14: 0000000000400430 0 SECTION LOCAL DEFAULT 14
15: 00000000004005f4 0 SECTION LOCAL DEFAULT 15
16: 0000000000400600 0 SECTION LOCAL DEFAULT 16
17: 0000000000400628 0 SECTION LOCAL DEFAULT 17
18: 0000000000400668 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e10 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e18 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e20 0 SECTION LOCAL DEFAULT 21
22: 0000000000600e28 0 SECTION LOCAL DEFAULT 22
23: 0000000000600ff8 0 SECTION LOCAL DEFAULT 23
24: 0000000000601000 0 SECTION LOCAL DEFAULT 24
25: 0000000000601028 0 SECTION LOCAL DEFAULT 25
26: 0000000000601040 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
29: 0000000000600e20 0 OBJECT LOCAL DEFAULT 21 __JCR_LIST__
30: 0000000000400460 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
31: 00000000004004a0 0 FUNC LOCAL DEFAULT 14 register_tm_clones
32: 00000000004004e0 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
33: 0000000000601040 1 OBJECT LOCAL DEFAULT 26 completed.7593
34: 0000000000600e18 0 OBJECT LOCAL DEFAULT 20 __do_global_dtors_aux_fin
35: 0000000000400500 0 FUNC LOCAL DEFAULT 14 frame_dummy
36: 0000000000600e10 0 OBJECT LOCAL DEFAULT 19 __frame_dummy_init_array_
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo5.c
38: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar5.c
39: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
40: 0000000000400778 0 OBJECT LOCAL DEFAULT 18 __FRAME_END__
41: 0000000000600e20 0 OBJECT LOCAL DEFAULT 21 __JCR_END__
42: 0000000000000000 0 FILE LOCAL DEFAULT ABS
43: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
44: 0000000000600e28 0 OBJECT LOCAL DEFAULT 22 _DYNAMIC
45: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
46: 0000000000400628 0 NOTYPE LOCAL DEFAULT 17 __GNU_EH_FRAME_HDR
47: 0000000000601000 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
48: 00000000004005f0 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
49: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
50: 0000000000601028 0 NOTYPE WEAK DEFAULT 25 data_start
51: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 _edata
52: 0000000000601038 4 OBJECT GLOBAL DEFAULT 25 x
53: 00000000004005f4 0 FUNC GLOBAL DEFAULT 15 _fini
54: 0000000000400567 23 FUNC GLOBAL DEFAULT 14 f
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
57: 0000000000601028 0 NOTYPE GLOBAL DEFAULT 25 __data_start
58: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
59: 0000000000601030 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
60: 0000000000400600 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
61: 0000000000400580 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
62: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 26 _end
63: 0000000000400430 42 FUNC GLOBAL DEFAULT 14 _start
64: 000000000060103c 4 OBJECT GLOBAL DEFAULT 25 y
65: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
66: 0000000000400526 65 FUNC GLOBAL DEFAULT 14 main
67: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
68: 0000000000601040 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
69: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
70: 00000000004003c8 0 FUNC GLOBAL DEFAULT 11 _init
2)修改方案
修改方案:只将 bar5.c 中的 x 的作用域由全局作用域改为局部作用域,即用关键字static
修饰。
修改后的 bar5.c:
static double x;
void f()
{
x = -0.0;
}
foo5.c 与原来的保持一致。
编译并运行:
$ gcc -o foo5 foo5.c bar5.c
$ ./foo5
x = 0x3b6d(15213) y = 0x3b6c(15212)
查看 foo5 的符号表:
$ readelf -s foo5
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
Symbol table '.symtab' contains 72 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002b8 0 SECTION LOCAL DEFAULT 5
6: 0000000000400318 0 SECTION LOCAL DEFAULT 6
7: 0000000000400358 0 SECTION LOCAL DEFAULT 7
8: 0000000000400360 0 SECTION LOCAL DEFAULT 8
9: 0000000000400380 0 SECTION LOCAL DEFAULT 9
10: 0000000000400398 0 SECTION LOCAL DEFAULT 10
11: 00000000004003c8 0 SECTION LOCAL DEFAULT 11
12: 00000000004003f0 0 SECTION LOCAL DEFAULT 12
13: 0000000000400420 0 SECTION LOCAL DEFAULT 13
14: 0000000000400430 0 SECTION LOCAL DEFAULT 14
15: 00000000004005f4 0 SECTION LOCAL DEFAULT 15
16: 0000000000400600 0 SECTION LOCAL DEFAULT 16
17: 0000000000400628 0 SECTION LOCAL DEFAULT 17
18: 0000000000400668 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e10 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e18 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e20 0 SECTION LOCAL DEFAULT 21
22: 0000000000600e28 0 SECTION LOCAL DEFAULT 22
23: 0000000000600ff8 0 SECTION LOCAL DEFAULT 23
24: 0000000000601000 0 SECTION LOCAL DEFAULT 24
25: 0000000000601028 0 SECTION LOCAL DEFAULT 25
26: 0000000000601040 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
29: 0000000000600e20 0 OBJECT LOCAL DEFAULT 21 __JCR_LIST__
30: 0000000000400460 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
31: 00000000004004a0 0 FUNC LOCAL DEFAULT 14 register_tm_clones
32: 00000000004004e0 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
33: 0000000000601040 1 OBJECT LOCAL DEFAULT 26 completed.7593
34: 0000000000600e18 0 OBJECT LOCAL DEFAULT 20 __do_global_dtors_aux_fin
35: 0000000000400500 0 FUNC LOCAL DEFAULT 14 frame_dummy
36: 0000000000600e10 0 OBJECT LOCAL DEFAULT 19 __frame_dummy_init_array_
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo5.c
38: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar5.c
39: 0000000000601048 8 OBJECT LOCAL DEFAULT 26 x
40: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
41: 0000000000400778 0 OBJECT LOCAL DEFAULT 18 __FRAME_END__
42: 0000000000600e20 0 OBJECT LOCAL DEFAULT 21 __JCR_END__
43: 0000000000000000 0 FILE LOCAL DEFAULT ABS
44: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
45: 0000000000600e28 0 OBJECT LOCAL DEFAULT 22 _DYNAMIC
46: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
47: 0000000000400628 0 NOTYPE LOCAL DEFAULT 17 __GNU_EH_FRAME_HDR
48: 0000000000601000 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
49: 00000000004005f0 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
50: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
51: 0000000000601028 0 NOTYPE WEAK DEFAULT 25 data_start
52: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 _edata
53: 0000000000601038 4 OBJECT GLOBAL DEFAULT 25 x
54: 00000000004005f4 0 FUNC GLOBAL DEFAULT 15 _fini
55: 0000000000400567 23 FUNC GLOBAL DEFAULT 14 f
56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
57: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
58: 0000000000601028 0 NOTYPE GLOBAL DEFAULT 25 __data_start
59: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
60: 0000000000601030 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
61: 0000000000400600 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
62: 0000000000400580 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
63: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 26 _end
64: 0000000000400430 42 FUNC GLOBAL DEFAULT 14 _start
65: 000000000060103c 4 OBJECT GLOBAL DEFAULT 25 y
66: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
67: 0000000000400526 65 FUNC GLOBAL DEFAULT 14 main
68: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
69: 0000000000601040 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
70: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
71: 00000000004003c8 0 FUNC GLOBAL DEFAULT 11 _init
注意:修改后的可执行目标文件foo5
中符号名称为x
的符号有两个。一个是全局作用域的,一个是局部作用域的。而修改前的foo5
中符号名称为x
的符号只有一个。
其他修改方案:只将 foo5.c 中的 x 的作用域由全局作用域改为局部作用域,即用关键字static
修饰。关于该方案的可行性,有兴趣的可以自己验证一下。
a)A 中 Module 1 和 Module 2 的代码
m1_a.c(Module 1):
int main()
{
}
m2_a.c(Module 2):
static int main = 1;
int p2()
{
}
注意:这里将原书中 Module 2 里面的]
改为;
,应该是原书作者的笔误。
生成可重定位目标文件m1_a.o
:
$ gcc -c m1_a.c
生成可重定位目标文件m2_a.o
:
$ gcc -c m2_a.c
生成可执行目标文件m1_a
:
$ gcc -o m1_a m1_a.o m2_a.o
成功生成了可执行目标文件m1_a
,即未出现链接错误。
查看可重定位目标文件m1_a.o
的符号表:
$ readelf -s m1_a.o
Symbol table '.symtab' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m1_a.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 main
可以看出,Module 1 中的符号引用main
的定义在 Module 1 中的.text
section 中。
查看可重定位目标文件m2_a.o
的符号表:
$ readelf -s m2_a.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m2_a.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 2 main
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5
7: 0000000000000000 0 SECTION LOCAL DEFAULT 6
8: 0000000000000000 0 SECTION LOCAL DEFAULT 4
9: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 p2
可以看出,Module 2 中的符号引用main
的定义在 Module 2 中的.data
section 中。
b)B 中 Module 1 和 Module 2 的代码
m1_b.c(Module 1):
int x;
void main()
{
}
m2_b.c(Module 2):
double x;
int p2()
{
}
生成可重定位目标文件m1_b.o
:
$ gcc -c m1_b.c
生成可重定位目标文件m2_b.o
:
$ gcc -c m2_b.c
生成可执行目标文件m1_b
:
$ gcc -o m1_b m1_b.o m2_b.o
成功生成了可执行目标文件m1_b
,即未出现链接错误。
查看可重定位目标文件m1_b.o
的符号表:
$ readelf -s m1_b.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m1_b.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM x
9: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 main
可以看出,Module 1 中的符号引用x
被标记为COMMON
。
查看可重定位目标文件m2_b.o
的符号表:
$ readelf -s m2_b.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m2_b.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000008 8 OBJECT GLOBAL DEFAULT COM x
9: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 p2
可以看出,Module 2 中的符号引用x
也被标记为COMMON
。
因此,符号引用x
的定义在 Module 1 或 Module 2 中任意选择一个。
c)C 中 Module 1 和 Module 2 的代码
m1_c.c(Module 1):
int x = 1;
void main()
{
}
m2_c.c(Module 2):
double x = 1.0;
int p2()
{
}
生成可重定位目标文件m1_c.o
:
$ gcc -c m1_c.c
生成可重定位目标文件m2_c.o
:
$ gcc -c m2_c.c
生成可执行目标文件m1_c
:
$ gcc -o m1_c m1_c.o m2_c.o
m2_c.o:(.data+0x0): multiple definition of `x'
m1_c.o:(.data+0x0): first defined here
/usr/bin/x86_64-linux-gnu-ld: Warning: size of symbol `x' changed from 4 in m1_c.o to 8 in m2_c.o
collect2: error: ld returned 1 exit status
可以看出,在生成可执行目标文件m1_c
时,出现了链接错误。
查看可重定位目标文件m1_c.o
的符号表:
$ readelf -s m1_c.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m1_c.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 x
9: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 main
可以看出,Module 1 中的符号引用x
的定义在 Module 1 中,并且为全局作用域。
查看可重定位目标文件m2_c.o
的符号表:
$ readelf -s m2_c.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS m2_c.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
8: 0000000000000000 8 OBJECT GLOBAL DEFAULT 2 x
9: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 p2
可以看出,Module 2 中的符号引用x
的定义在 Module 2 中,并且为全局作用域。
也就是说,Module 1 和 Module 2 中重复定义了全局变量x
。所以,在生成可执行目标文件m1_c
时,出现了链接错误。
因此,该题的解答为:
题目 | 解答 |
---|---|
A | |
B | |
C |
a)生成并执行可执行目标文件
foo6.c:
void p2();
int main()
{
p2();
return 0;
}
bar6.c:
char main;
void p2()
{
printf("main=0x%x\n", main);
}
生成可重定位目标文件foo6.o
:
$ gcc -c foo6.c
生成可重定位目标文件bar6.o
:
$ gcc -c bar6.c
生成并执行可执行目标文件foo6
:
$ gcc -o foo6 foo6.o bar6.o
$ ./foo6
main=0x55
注意:在我们的系统中(X86-64 Linux, Little-endian),按照上述步骤生成可执行目标文件foo6
时,打印的main
变量的值为 0x55,而不是题目中的 0x48。要想打印的main
变量的值为题目中的 0x48,生成可执行目标文件foo6_Og
的方式应该为gcc -o foo6_Og bar6.c foo6.c -Og
。
查看foo6_Og
中main
函数的汇编代码(部分):
$ objdump -d foo6_Og
foo6_Og: file format elf64-x86-64
# 省略...
Disassembly of section .text:
# 省略...
000000000040056a <main>:
40056a: 48 83 ec 08 sub $0x8,%rsp
40056e: b8 00 00 00 00 mov $0x0,%eax
400573: e8 ce ff ff ff callq 400546 <p2>
400578: b8 00 00 00 00 mov $0x0,%eax
40057d: 48 83 c4 08 add $0x8,%rsp
400581: c3 retq
400582: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400589: 00 00 00
40058c: 0f 1f 40 00 nopl 0x0(%rax)
# 省略...
可以看出,可执行目标文件foo6_Og
中main
函数的第一个字节的值为 0x48,符合题目中的 0x48。
b)为什么执行可执行目标文件foo6
时,打印的main
变量的值为 0x55
查看可重定位目标文件foo6.o
的符号表:
$ readelf -s foo6.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo6.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 6
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 5
8: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 main
9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND p2
可以看出,foo6.c
中符号引用main
所对应的符号定义在foo6.o
的.text
section 中。
查看可重定位目标文件bar6.o
的符号表:
$ readelf -s bar6.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar6.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000001 1 OBJECT GLOBAL DEFAULT COM main
10: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 p2
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
可以看出,bar6.c
中符号引用main
所对应的符号定义还未确定。
查看可执行目标文件foo6
的汇编代码(部分):
$ objdump -d foo6
foo6: file format elf64-x86-64
# 省略...
Disassembly of section .text:
# 省略...
0000000000400526 <main>:
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: b8 00 00 00 00 mov $0x0,%eax
40052f: e8 07 00 00 00 callq 40053b <p2>
400534: b8 00 00 00 00 mov $0x0,%eax
400539: 5d pop %rbp
40053a: c3 retq
# 省略...
可以看出,main
函数的汇编代码中第一个字节的值为 0x55,与打印的main
变量的值相同。也就是说,main
变量符号引用所对应的符号定义为main
函数的符号。所以,执行可执行目标文件foo6
时,打印的main
变量的值为 0x55。另外,可以通过 gdb 调试验证main 变量符号引用的地址就是 main 函数的地址
,具体过程如下:
$ gcc -o foo6_g foo6.c bar6.c -g
$ gdb -q ./foo6_g
Reading symbols from ./foo6_g...done.
(gdb) start
Temporary breakpoint 1 at 0x40052a: file foo6.c, line 7.
Starting program: /home/xuxiaoqiang/tx/7/7.6/foo6_g
Temporary breakpoint 1, main () at foo6.c:7
7 p2();
(gdb) l
2
3 void p2();
4
5 int main()
6 {
7 p2();
8 return 0;
9 }
(gdb) b bar6.c:7
Breakpoint 2 at 0x40053f: file bar6.c, line 7.
(gdb) c
Continuing.
Breakpoint 2, p2 () at bar6.c:7
7 printf("main=0x%x\n", main);
(gdb) x/bx main
0x400526 <main>: 0x55
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400526 <+0>: push %rbp
0x0000000000400527 <+1>: mov %rsp,%rbp
0x000000000040052a <+4>: mov $0x0,%eax
0x000000000040052f <+9>: callq 0x40053b <p2>
0x0000000000400534 <+14>: mov $0x0,%eax
0x0000000000400539 <+19>: pop %rbp
0x000000000040053a <+20>: retq
End of assembler dump.
(gdb)
可以看出,在bar6.c
中打印main
变量的值时,main
变量的地址为 0x400526,即main
函数的地址。
对于 A:p.o libx.a
对于 B:p.o libx.a liby.a libx.a
对于 C:p.o libx.a liby.a libz.a libx.a
a)生成可执行目标文件foo6
作为研究案例
具体生成步骤与 7.9解答 中的一致。
b)查看可执行目标文件foo6
的程序表
$ readelf -l foo6
Elf file type is EXEC (Executable file)
Entry point 0x400430
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000744 0x0000000000000744 R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000228 0x0000000000000230 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000005f0 0x00000000004005f0 0x00000000004005f0
0x000000000000003c 0x000000000000003c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
可以看出,数据段(data segment)的起始地址为 0x0000000000600e10,目标文件中的段大小为 0x0000000000000228,内存中的段大小为 0x0000000000000230。也就是题目中所说的程序头部表表明数据段占用了内存中 0x230 个字节。然后,其中只有开始的 0x228 字节来自可执行目标文件的节
。
c)是什么引起了这种差异?
查看可执行目标文件foo6
的符号表:
$ readelf -S foo6
There are 31 section headers, starting at offset 0x1a18:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
00000000000001a2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005d4 000005d4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004005e0 000005e0
000000000000000f 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004005f0 000005f0
000000000000003c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400630 00000630
0000000000000114 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601038 00001038
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00001038
0000000000000032 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00001907
000000000000010c 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00001070
0000000000000678 0000000000000018 30 48 8
[30] .strtab STRTAB 0000000000000000 000016e8
000000000000021f 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
从步骤b)
中打印的程序表可以看出,数据段包含的 sections 有:.init_array、.fini_array、.jcr .dynamic、.got、.got.plt、.data、.bss 。
这些 sections 的大小(字节) = 0x601038 + 0x8 - 0x60e10 = 0x230(0x601038 为 .bss section 在目标文件中偏移量,0x8 为 .bss section 的大小,0x60e10 为 ..init_array section 在目标文件中偏移量)。
也就是说,这些 sections 在内存中会占用 0x230 字节,符合程序表中数据段占用的内存大小。由于数据段包含了.bss
section(大小为 8 字节),而该 section 不占用目标文件的空间。因此,只有 0x228 字节来自可执行目标文件的节。
根据公式 VALUE(reference) = ADDR(defined_symbol) + VALUE(r.addend) - ADDR(reference):
对于 A:
callq 指令中对 swap 的重定位引用的值 = swap 符号定义的运行时地址 + r.addend - swap 符号引用的运行时地址
= 0x4004f8 + (-4) - (.text section 在可执行目标文件中的地址 + swap 符号引用在 .text section 中的偏移量)
= 0x4004f8 + (-4) - (0x4004e0 + 0xa)
= 0xa
对于 B:
callq 指令中对 swap 的重定位引用的值 = swap 符号定义的运行时地址 + r.addend - swap 符号引用的运行时地址
= 0x400500 + (-4) - (.text section 在可执行目标文件中的地址 + swap 符号引用在 .text section 中的偏移量)
= 0x400500 + (-4) - (0x4004d0 + 0xa)
= 0x22
对于 A:
# 查看 libc.a 中可重定位目标文件的数量
$ ar t /usr/aarch64-linux-gnu/lib/libc.a | grep ".o" -c
1522
# 查看 libm.a 中可重定位目标文件的数量
$ ar t /usr/aarch64-linux-gnu/lib/libm.a | grep ".o" -c
431
对于 B:在我们的系统中(X86-64 Linux, Little-endian),gcc -Og 产生的可执行代码与 gcc -Og -g 产生的是相同的。
对于 C:
# 查看 gcc-6 使用的共享库
$ ldd /usr/bin/gcc-6
linux-vdso.so.1 => (0x00007ffc6178d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f58462a6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5846670000)
# 查看 g++-6 使用的共享库
$ ldd /usr/bin/g++-6
linux-vdso.so.1 => (0x00007ffc129d4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f779003f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7790409000)
下一篇:计算机系统篇之链接(13):升级共享库导致程序运行时错误的惨痛经历