C语言的static究竟限制了谁?介绍一种绕开static限制的方法

电子说

1.2w人已加入

描述

1 问题来源

就在上个星期,在群里收到一个有关static的讨论,我个人觉得挺有意思的,于是打算将其整理成文,希望能够帮助大家更好地理解和应用static这个关键字。

群里的原问题如下:

大佬们,问一个比较低智问题,static修饰的变量不是只能被当前文件所调用吗?为什么这里在其他.c文件可以调用get_board_led_opr这个函数获取board_demo_led_opr这个变量的值。

ChatGPT

2 关于上述问题的答疑
这个问题群里也给出了一些参考答案,而我一开始的回答,并没有直接回答答案,而是随手提出了3个问题:

问自己几个问题:
1.get函数返回什么地址(各种地址)语法上有约束条款吗?
2.static修饰的全局变量存放在哪里?它的地址是什么样的?
3.c语言里面要想访问任意一个地址,从语法层面上,能阻止得了吗?

提问者的回答:

(1)get_board_led_opr函数返回的是一个自定义的结构体get_board_led_opr。但是get_board_led_opr这个结构体被static修饰了。
(2)get_board_led_opr结构体放在了board_demo.c中,而调用get_board_led_opr函数是在leddrv.c中。
(3)我询问了另外一个群里面的大佬,他们说,static仅仅在编译期间有用。在运行状态是无效果的。也就是说,虽然leddrv.c无法直接使用get_board_led_opr这个结构体。但是可以利用get_board_led_opr函数,间接的获取get_board_led_opr这个结构体,因为get_board_led_opr函数和et_board_led_opr这个结构体在同一个.c文件中

上述问题的点关键在于理解两个:

被static修饰之后,这个变量变成了什么?
C语言的万能指针。
首先,被static修饰之后,意味着只有 当前的C语言C文件 可以直接 引用访问它,但是并不代表外部的模块(除当前C文件外的其他C文件)就不能访问它。

直接访问肯定是不行的,但是 间接 的方式肯定是可以的。

它的方法就是如上图的代码片段那样,将static变量的地址,以指针的形式传出去给其他模块即可。

这里还要理解的一点是,被static修饰的变量是存放在全局的数据区,它的有效生存空间是整一个C工程运行时间内都是有效的。

所以你通过指针去访问它,就变成了可能;这也是我们学习指针时,提到的 间接访问 的最真实体验。

3 教科书里的static
乘此机会,我们来复习一下C语言教科书,对于static关键字,它们是怎么描述的?

由于手头标准的教科书,已很难找了,索性问一下ChatGPT,看看它的总结:

在C语言中,static关键字有两种主要的用法:静态变量和静态函数。

静态变量:

静态变量是在程序运行期间一直存在的变量,其生命周期从声明处到程序结束。静态变量被存储在静态数据区,不会受到函数的调用和返回的影响,每次进入函数都会保留上一次的值。静态变量可以在函数内部或者全局作用域中声明。在函数内部声明的静态变量只能在该函数内部访问,而在全局作用域中声明的静态变量可以在整个文件中访问。 静态变量的主要作用: - 维护变量的持久状态,保持值的连续性。 - 限制变量的作用域,只能在函数内或者文件内访问。 - 避免与同名的全局变量产生冲突。 示例代码:

void func()

{

​ static int count = 0; // 定义一个静态变量

​ count++;

​ printf(“Count: %dn”, count);

}

int main()

{

​ func(); // 输出:Count: 1

​ func(); // 输出:Count: 2

​ func(); // 输出:Count: 3

​ return 0;

}

静态函数:

静态函数是只能在当前源文件中使用的函数,其作用于只限于声明所在的文件,无法被其他文件调用。使用static关键字修饰函数,可以实现函数的隐藏和封装。因此,静态函数的主要作用是限制函数的作用域,防止其他文件访问该函数,避免命名冲突,并且提高代码的可读性和可维护性。 示例代码:

static void util_func(int value)

{

​ printf(“Value: %dn”, value);

}

int main()

{

​ util_func(10); // 可以在当前文件中调用util_func函数

​ return 0;

}

总结:static关键字在C语言中的用法主要有静态变量和静态函数。静态变量用于维护变量的持久状态和限制作用域,静态函数用于限制函数的作用域和提高代码的可读性。

基本从ChatGPT的总结,可以了解清楚static的使用;不过它上面未提及到的一点是:

在static修饰的静态变量中,有两种情况,一种是修饰全局的变量,叫静态全局变量;另一种是修饰局部变量,叫静态局部变量(其实如果从作用周期的时间来看,它也是全局的)。

ChatGPT中演示的静态局部变量,它的作用域就仅限于定义变量的这个函数内(准确说是,定义变量的大括号范围内);一旦出了这个作用域,是没有办法访问到的。

而静态全局变量,它的作用域又比静态局部变量更宽泛些,它可以允许定义静态全局变量的整个C文件内,都可以访问它;但是之外的,其他C文件是无法访问的。

综上,static关键字,主要强调的是 静态的 这个含义,无非就是作用域的大小问题。

4 C语言的static究竟限制了谁?
从编译原理和编译流程来拆解这个,问题的答案就很快就能出来。

我们就以第3小节中,ChatGPT给出的示例代码为例,深度拆解一下这个static。

在正式拆解之前,需要具备一些前置知识:C语言的编译流程。

曾经,我写过一篇相关的文章,大家可以提前参考下,一文带你了解C代码到底是如何被编译的

在这里,我不打算一一搬过来,仅仅把一张图贴过来参考:

ChatGPT

我们顺着这个思路来拆解跟踪一下代码里面的 static。

编写C代码(C源代码里面的static)
main.c
#include
static int g_count = 0;
void func()
{
static int l_count = 0; // 定义一个静态局部变量
l_count++;
g_count++;
printf("l_count: %d, g_count: %dn", l_count, g_count);
}
static void util_func(int value) //定义一个静态函数
{
printf("Value: %dn", value);
}
extern void test_func(void);
int main()
{
func();//输出:l_count:1
func();//输出:l_count:2
func();//输出:l_count:3
util_func(10);//可以在当前文件中调用util_func函数,输出:Value: 10
test_func(); // 调用test.c中的函数
return 0;
}
test.c
// 假设它们都是可以访问的
extern int l_count;
extern int g_count;
extern void util_func(int value);
void test_func(void)
{
#if 0
l_count++; // 访问l_count静态局部变量,编译报错
g_count++; // 访问g_count静态全局变量,编译报错
util_func(100); // 调用main.c中的static函数,编译报错
#endif
}
当test_func中的if-0打开后,根据我们上面的理论分析,肯定是编译不过的,如下:

test_static$ gcc -o test *.c
/usr/bin/ld: /tmp/ccawkyDR.o: in function test_func': test.c:(.text+0xa): undefined reference to l_count'
/usr/bin/ld: test.c:(.text+0x13): undefined reference to l_count' /usr/bin/ld: test.c:(.text+0x19): undefined reference to g_count'
/usr/bin/ld: test.c:(.text+0x22): undefined reference to g_count' /usr/bin/ld: test.c:(.text+0x2c): undefined reference to util_func'
collect2: error: ld returned 1 exit status
像这种就是很明显的找不到变量或函数的实现体,从而出现找不到引用的 链接 错误。

去掉这些引用后(if-0)打开,即可编译成功,且能够正常运行。

test_static$ gcc -o test *.c
test_static$ ./test
l_count: 1, g_count: 1
l_count: 2, g_count: 2
l_count: 3, g_count: 3
Value: 10
分步拆解1:先看预处理后static的情况
使用的命令下:gcc -c -o main.o main.c -save-temps=obj

这时候我们可以看到 main.i main.s main.o 这几个文件。

先看main.i 的内容:

忽略前面对头文件内容的搬运

873 "/usr/include/stdio.h" 3 4

2 "main.c" 2

3 "main.c"

static int g_count = 0;
void func()
{
static int l_count = 0;
l_count++;
g_count++;
printf("l_count: %d, g_count: %dn", l_count, g_count);
}
static void util_func(int value)
{
printf("Value: %dn", value);
}
extern void test_func(void);
int main()
{
func();
func();
func();
util_func(10);
test_func();
return 0;

从上基本可以看到,static还是在的,并且整一个预处理后的代码,基本保留了源代码的样子(原因是我们并没有使用类似宏定义的内容)。

拆解第2步:看看汇编文件有没有static的影子

这时候我们要查看的是 main.s 文件:

test_static$ cat main.s
.file "main.c"
.text
.local g_count
.comm g_count,4,4
.section .rodata
.LC0:
.string "l_count: %d, g_count: %dn"
.text
.globl func
.type func, @function
func:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl l_count.2316(%rip), %eax
addl $1, %eax
movl %eax, l_count.2316(%rip)
movl g_count(%rip), %eax
addl $1, %eax
movl %eax, g_count(%rip)
movl g_count(%rip), %edx
movl l_count.2316(%rip), %eax
movl %eax, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size func, .-func
.section .rodata
.LC1:
.string "Value: %dn"
.text
.type util_func, @function
util_func:
.LFB1:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size util_func, .-util_func
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $0, %eax
call func
movl $0, %eax
call func
movl $0, %eax
call func
movl $10, %edi
call util_func
call test_func@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size main, .-main
.local l_count.2316
.comm l_count.2316,4,4
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
初一打开,可能有种不太认识的感觉,没关系,我们先找到我们能认识的东西。

先找到被static修饰过的内容(静态全局变量、静态局部变量、静态函数)。

1)静态全局变量g_count:

.file   "main.c"
    .text
    .local  g_count
    .comm   g_count,4,4
    .section        .rodata

2)静态局部变量l_count:

.LFE2:
.size main, .-main
.local l_count.2316
.comm l_count.2316,4,4
3)静态函数util_func:

.string "Value: %dn"
    .text
    .type   util_func, @function

util_func:
.LFB1:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
leaq .LC1(%rip), %rdi
movl $0, %eax
call printf@PLT
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc

由于我们只是对main.c进行汇编,所以看不出全貌,但是能够确认的是,在汇编文件中,static已经消失了,换句话说,接下来就没static啥事情了,而是转为其他信息了。我们接着往下拆解。

拆解第3步:obj文件里面确认没有static的要素吗?

我们看下main.o文件,由于它不是可读文件,所以得用其他指令分析下:

test_static$ readelf -a main.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1520 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
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
00000000000000ac 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000003e0
0000000000000150 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000ec
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000ec
0000000000000008 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000ec
0000000000000025 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000111
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000013d
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000140
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000160
0000000000000078 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000530
0000000000000048 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 000001d8
00000000000001b0 0000000000000018 12 13 8
[12] .strtab STRTAB 0000000000000000 00000388
0000000000000053 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000578
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
There are no program headers in this file.
There is no dynamic section in this file.
Relocation section '.rela.text' at offset 0x3e0 contains 14 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000a 000400000002 R_X86_64_PC32 0000000000000000 .bss + 0
000000000013 000400000002 R_X86_64_PC32 0000000000000000 .bss + 0
000000000019 000400000002 R_X86_64_PC32 0000000000000000 .bss - 4
000000000022 000400000002 R_X86_64_PC32 0000000000000000 .bss - 4
000000000028 000400000002 R_X86_64_PC32 0000000000000000 .bss - 4
00000000002e 000400000002 R_X86_64_PC32 0000000000000000 .bss + 0
000000000037 000600000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000041 000f00000004 R_X86_64_PLT32 0000000000000000 printf - 4
00000000005f 000600000002 R_X86_64_PC32 0000000000000000 .rodata + 16
000000000069 000f00000004 R_X86_64_PLT32 0000000000000000 printf - 4
00000000007e 000d00000004 R_X86_64_PLT32 0000000000000000 func - 4
000000000088 000d00000004 R_X86_64_PLT32 0000000000000000 func - 4
000000000092 000d00000004 R_X86_64_PLT32 0000000000000000 func - 4
0000000000a1 001100000004 R_X86_64_PLT32 0000000000000000 test_func - 4
Relocation section '.rela.eh_frame' at offset 0x530 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 48
000000000060 000200000002 R_X86_64_PC32 0000000000000000 .text + 70
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Symbol table '.symtab' contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 g_count
6: 0000000000000000 0 SECTION LOCAL DEFAULT 5
7: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 l_count.2316
8: 0000000000000048 40 FUNC LOCAL DEFAULT 1 util_func
9: 0000000000000000 0 SECTION LOCAL DEFAULT 7
10: 0000000000000000 0 SECTION LOCAL DEFAULT 8
11: 0000000000000000 0 SECTION LOCAL DEFAULT 9
12: 0000000000000000 0 SECTION LOCAL DEFAULT 6
13: 0000000000000000 72 FUNC GLOBAL DEFAULT 1 func
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND GLOBAL_OFFSET_TABLE
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
16: 0000000000000070 60 FUNC GLOBAL DEFAULT 1 main
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND test_func
No version information found in this file.
Displaying notes found in: .note.gnu.property
Owner Data size Description
GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0
Properties: x86 feature: IBT, SHSTK
recan@ubuntu:~/win_share_workspace/test_share/test_static$
从上面可以看到,g_count、l_count、util_func 都被 LOCAL 修饰了,而非 GLOBAL 的:

5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 g_count
 6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
 7: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 l_count.2316
 8: 0000000000000048    40 FUNC    LOCAL  DEFAULT    1 util_func

究竟这两者修饰有啥区别,会产生什么影响呢?我们接着往下拆解。

拆解第4步:链接后的可执行文件反汇编
前面也说到了,由于我们都只看到 main这个C文件相关 预处理、汇编、obj内容,而没有看到整个可执行文件的全貌。

执行反汇编看看:

test_static$ objdump -l -d -x -s -S test
test: file format elf64-x86-64
test
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000001060
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 23
filesz 0x00000000000002d8 memsz 0x00000000000002d8 flags r--
INTERP off 0x0000000000000318 vaddr 0x0000000000000318 paddr 0x0000000000000318 align 2
0
filesz 0x000000000000001c memsz 0x000000000000001c flags r--
LOAD off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 212
filesz 0x0000000000000600 memsz 0x0000000000000600 flags r--
LOAD off 0x0000000000001000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2
12
filesz 0x0000000000000285 memsz 0x0000000000000285 flags r-x
LOAD off 0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 212
filesz 0x00000000000001f0 memsz 0x00000000000001f0 flags r--
LOAD off 0x0000000000002db8 vaddr 0x0000000000003db8 paddr 0x0000000000003db8 align 2
12
filesz 0x0000000000000258 memsz 0x0000000000000268 flags rw-
DYNAMIC off 0x0000000000002dc8 vaddr 0x0000000000003dc8 paddr 0x0000000000003dc8 align 23
filesz 0x00000000000001f0 memsz 0x00000000000001f0 flags rw-
NOTE off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2
3
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
NOTE off 0x0000000000000358 vaddr 0x0000000000000358 paddr 0x0000000000000358 align 22
filesz 0x0000000000000044 memsz 0x0000000000000044 flags r--
0x6474e553 off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2
3
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r--
EH_FRAME off 0x000000000000202c vaddr 0x000000000000202c paddr 0x000000000000202c align 22
filesz 0x000000000000005c memsz 0x000000000000005c flags r--
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2
4
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
RELRO off 0x0000000000002db8 vaddr 0x0000000000003db8 paddr 0x0000000000003db8 align 20
filesz 0x0000000000000248 memsz 0x0000000000000248 flags r--
Dynamic Section:
NEEDED libc.so.6
INIT 0x0000000000001000
FINI 0x0000000000001278
INIT_ARRAY 0x0000000000003db8
INIT_ARRAYSZ 0x0000000000000008
FINI_ARRAY 0x0000000000003dc0
FINI_ARRAYSZ 0x0000000000000008
GNU_HASH 0x00000000000003a0
STRTAB 0x0000000000000470
SYMTAB 0x00000000000003c8
STRSZ 0x0000000000000084
SYMENT 0x0000000000000018
DEBUG 0x0000000000000000
PLTGOT 0x0000000000003fb8
PLTRELSZ 0x0000000000000018
PLTREL 0x0000000000000007
JMPREL 0x00000000000005e8
RELA 0x0000000000000528
RELASZ 0x00000000000000c0
RELAENT 0x0000000000000018
FLAGS 0x0000000000000008
FLAGS_1 0x0000000008000001
VERNEED 0x0000000000000508
VERNEEDNUM 0x0000000000000001
VERSYM 0x00000000000004f4
RELACOUNT 0x0000000000000003
Version References:
required from libc.so.6:
0x09691a75 0x00 02 GLIBC_2.2.5
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000000318 0000000000000318 00000318 2
0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.gnu.property 00000020 0000000000000338 0000000000000338 00000338 23
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000000358 0000000000000358 00000358 2
2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .note.ABI-tag 00000020 000000000000037c 000000000000037c 0000037c 22
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .gnu.hash 00000024 00000000000003a0 00000000000003a0 000003a0 2
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynsym 000000a8 00000000000003c8 00000000000003c8 000003c8 23
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .dynstr 00000084 0000000000000470 0000000000000470 00000470 2
0
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version 0000000e 00000000000004f4 00000000000004f4 000004f4 21
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .gnu.version_r 00000020 0000000000000508 0000000000000508 00000508 2
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .rela.dyn 000000c0 0000000000000528 0000000000000528 00000528 23
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .rela.plt 00000018 00000000000005e8 00000000000005e8 000005e8 2
3
CONTENTS, ALLOC, LOAD, READONLY, DATA
11 .init 0000001b 0000000000001000 0000000000001000 00001000 22
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 .plt 00000020 0000000000001020 0000000000001020 00001020 2
4
CONTENTS, ALLOC, LOAD, READONLY, CODE
13 .plt.got 00000010 0000000000001040 0000000000001040 00001040 24
CONTENTS, ALLOC, LOAD, READONLY, CODE
14 .plt.sec 00000010 0000000000001050 0000000000001050 00001050 2
4
CONTENTS, ALLOC, LOAD, READONLY, CODE
15 .text 00000215 0000000000001060 0000000000001060 00001060 24
CONTENTS, ALLOC, LOAD, READONLY, CODE
16 .fini 0000000d 0000000000001278 0000000000001278 00001278 2
2
CONTENTS, ALLOC, LOAD, READONLY, CODE
17 .rodata 00000029 0000000000002000 0000000000002000 00002000 22
CONTENTS, ALLOC, LOAD, READONLY, DATA
18 .eh_frame_hdr 0000005c 000000000000202c 000000000000202c 0000202c 2
2
CONTENTS, ALLOC, LOAD, READONLY, DATA
19 .eh_frame 00000168 0000000000002088 0000000000002088 00002088 23
CONTENTS, ALLOC, LOAD, READONLY, DATA
20 .init_array 00000008 0000000000003db8 0000000000003db8 00002db8 2
3
CONTENTS, ALLOC, LOAD, DATA
21 .fini_array 00000008 0000000000003dc0 0000000000003dc0 00002dc0 23
CONTENTS, ALLOC, LOAD, DATA
22 .dynamic 000001f0 0000000000003dc8 0000000000003dc8 00002dc8 2
3
CONTENTS, ALLOC, LOAD, DATA
23 .got 00000048 0000000000003fb8 0000000000003fb8 00002fb8 23
CONTENTS, ALLOC, LOAD, DATA
24 .data 00000010 0000000000004000 0000000000004000 00003000 2
3
CONTENTS, ALLOC, LOAD, DATA
25 .bss 00000010 0000000000004010 0000000000004010 00003010 22
ALLOC
26 .comment 0000002b 0000000000000000 0000000000000000 00003010 2
0
CONTENTS, READONLY
SYMBOL TABLE:
0000000000000318 l d .interp 0000000000000000 .interp
0000000000000338 l d .note.gnu.property 0000000000000000 .note.gnu.property
0000000000000358 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id
000000000000037c l d .note.ABI-tag 0000000000000000 .note.ABI-tag
00000000000003a0 l d .gnu.hash 0000000000000000 .gnu.hash
00000000000003c8 l d .dynsym 0000000000000000 .dynsym
0000000000000470 l d .dynstr 0000000000000000 .dynstr
00000000000004f4 l d .gnu.version 0000000000000000 .gnu.version
0000000000000508 l d .gnu.version_r 0000000000000000 .gnu.version_r
0000000000000528 l d .rela.dyn 0000000000000000 .rela.dyn
00000000000005e8 l d .rela.plt 0000000000000000 .rela.plt
0000000000001000 l d .init 0000000000000000 .init
0000000000001020 l d .plt 0000000000000000 .plt
0000000000001040 l d .plt.got 0000000000000000 .plt.got
0000000000001050 l d .plt.sec 0000000000000000 .plt.sec
0000000000001060 l d .text 0000000000000000 .text
0000000000001278 l d .fini 0000000000000000 .fini
0000000000002000 l d .rodata 0000000000000000 .rodata
000000000000202c l d .eh_frame_hdr 0000000000000000 .eh_frame_hdr
0000000000002088 l d .eh_frame 0000000000000000 .eh_frame
0000000000003db8 l d .init_array 0000000000000000 .init_array
0000000000003dc0 l d .fini_array 0000000000000000 .fini_array
0000000000003dc8 l d .dynamic 0000000000000000 .dynamic
0000000000003fb8 l d .got 0000000000000000 .got
0000000000004000 l d .data 0000000000000000 .data
0000000000004010 l d .bss 0000000000000000 .bss
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 l df ABS 0000000000000000 crtstuff.c
0000000000001090 l F .text 0000000000000000 deregister_tm_clones
00000000000010c0 l F .text 0000000000000000 register_tm_clones
0000000000001100 l F .text 0000000000000000 __do_global_dtors_aux
0000000000004010 l O .bss 0000000000000001 completed.8061
0000000000003dc0 l O .fini_array 0000000000000000 __do_global_dtors_aux_fini_array_entry
0000000000001140 l F .text 0000000000000000 frame_dummy
0000000000003db8 l O .init_array 0000000000000000 __frame_dummy_init_array_entry
0000000000000000 l df ABS 0000000000000000 main.c
0000000000004014 l O .bss 0000000000000004 g_count
0000000000004018 l O .bss 0000000000000004 l_count.2316
0000000000001191 l F .text 0000000000000028 util_func
0000000000000000 l df ABS 0000000000000000 test.c
0000000000000000 l df ABS 0000000000000000 crtstuff.c
00000000000021ec l O .eh_frame 0000000000000000 FRAME_END
0000000000000000 l df ABS 0000000000000000
0000000000003dc0 l .init_array 0000000000000000 __init_array_end
0000000000003dc8 l O .dynamic 0000000000000000 _DYNAMIC
0000000000003db8 l .init_array 0000000000000000 __init_array_start
000000000000202c l .eh_frame_hdr 0000000000000000 __GNU_EH_FRAME_HDR
0000000000003fb8 l O .got 0000000000000000 GLOBAL_OFFSET_TABLE
0000000000001000 l F .init 0000000000000000 _init
0000000000001270 g F .text 0000000000000005 __libc_csu_fini
0000000000000000 w UND 0000000000000000 _ITM_deregisterTMCloneTable
0000000000004000 w .data 0000000000000000 data_start
0000000000004010 g .data 0000000000000000 _edata
0000000000001278 g F .fini 0000000000000000 .hidden _fini
0000000000000000 F UND 0000000000000000 printf@@GLIBC_2.2.5
00000000000011f5 g F .text 000000000000000b test_func
0000000000000000 F UND 0000000000000000 __libc_start_main@@GLIBC_2.2.5
0000000000004000 g .data 0000000000000000 __data_start
0000000000000000 w UND 0000000000000000 gmon_start
0000000000004008 g O .data 0000000000000000 .hidden __dso_handle
0000000000002000 g O .rodata 0000000000000004 _IO_stdin_used
0000000000001149 g F .text 0000000000000048 func
0000000000001200 g F .text 0000000000000065 __libc_csu_init
0000000000004020 g .bss 0000000000000000 _end
0000000000001060 g F .text 000000000000002f _start
0000000000004010 g .bss 0000000000000000 __bss_start
00000000000011b9 g F .text 000000000000003c main
0000000000004010 g O .data 0000000000000000 .hidden TMC_END
0000000000000000 w UND 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w F UND 0000000000000000 __cxa_finalize@@GLIBC_2.2.5
Contents of section .interp:
0318 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-
0328 7838362d 36342e73 6f2e3200 x86-64.so.2.
省略了一些无关紧要的内容
Contents of section .data:
4000 00000000 00000000 08400000 00000000 .........@......
Contents of section .comment:
0000 4743433a 20285562 756e7475 20392e34 GCC: (Ubuntu 9.4
0010 2e302d31 7562756e 7475317e 32302e30 .0-1ubuntu1~20.0
0020 342e3129 20392e34 2e3000 4.1) 9.4.0.
Disassembly of section .init:
0000000000001000 <_init>:
_init():
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 < gmon_start >
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 callq *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 retq
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 9a 2f 00 00 pushq 0x2f9a(%rip) # 3fc0 < GLOBAL_OFFSET_TABLE +0x8>
1026: f2 ff 25 9b 2f 00 00 bnd jmpq *0x2f9b(%rip) # 3fc8 < GLOBAL_OFFSET_TABLE +0x10>
102d: 0f 1f 00 nopl (%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 pushq $0x0
1039: f2 e9 e1 ff ff ff bnd jmpq 1020 <.plt>
103f: 90 nop
Disassembly of section .plt.got:
0000000000001040 <__cxa_finalize@plt>:
1040: f3 0f 1e fa endbr64
1044: f2 ff 25 ad 2f 00 00 bnd jmpq *0x2fad(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
104b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .plt.sec:
0000000000001050 :
1050: f3 0f 1e fa endbr64
1054: f2 ff 25 75 2f 00 00 bnd jmpq *0x2f75(%rip) # 3fd0
105b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
0000000000001060 <_start>:
_start():
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp
1073: 4c 8d 05 f6 01 00 00 lea 0x1f6(%rip),%r8 # 1270 <__libc_csu_fini>
107a: 48 8d 0d 7f 01 00 00 lea 0x17f(%rip),%rcx # 1200 <__libc_csu_init>
1081: 48 8d 3d 31 01 00 00 lea 0x131(%rip),%rdi # 11b9

1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
108e: f4 hlt
108f: 90 nop
0000000000001090 :
deregister_tm_clones():
1090: 48 8d 3d 79 2f 00 00 lea 0x2f79(%rip),%rdi # 4010 < TMC_END >
1097: 48 8d 05 72 2f 00 00 lea 0x2f72(%rip),%rax # 4010 < TMC_END >
109e: 48 39 f8 cmp %rdi,%rax
10a1: 74 15 je 10b8
10a3: 48 8b 05 2e 2f 00 00 mov 0x2f2e(%rip),%rax # 3fd8 <_ITM_deregisterTMCloneTable>
10aa: 48 85 c0 test %rax,%rax
10ad: 74 09 je 10b8
10af: ff e0 jmpq *%rax
10b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
10b8: c3 retq
10b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010c0 :
register_tm_clones():
10c0: 48 8d 3d 49 2f 00 00 lea 0x2f49(%rip),%rdi # 4010 < TMC_END >
10c7: 48 8d 35 42 2f 00 00 lea 0x2f42(%rip),%rsi # 4010 < TMC_END >
10ce: 48 29 fe sub %rdi,%rsi
10d1: 48 89 f0 mov %rsi,%rax
10d4: 48 c1 ee 3f shr $0x3f,%rsi
10d8: 48 c1 f8 03 sar $0x3,%rax
10dc: 48 01 c6 add %rax,%rsi
10df: 48 d1 fe sar %rsi
10e2: 74 14 je 10f8
10e4: 48 8b 05 05 2f 00 00 mov 0x2f05(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable>
10eb: 48 85 c0 test %rax,%rax
10ee: 74 08 je 10f8
10f0: ff e0 jmpq *%rax
10f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10f8: c3 retq
10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001100 <__do_global_dtors_aux>:
__do_global_dtors_aux():
1100: f3 0f 1e fa endbr64
1104: 80 3d 05 2f 00 00 00 cmpb $0x0,0x2f05(%rip) # 4010 < TMC_END >
110b: 75 2b jne 1138 <__do_global_dtors_aux+0x38>
110d: 55 push %rbp
110e: 48 83 3d e2 2e 00 00 cmpq $0x0,0x2ee2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1115: 00
1116: 48 89 e5 mov %rsp,%rbp
1119: 74 0c je 1127 <__do_global_dtors_aux+0x27>
111b: 48 8b 3d e6 2e 00 00 mov 0x2ee6(%rip),%rdi # 4008 <__dso_handle>
1122: e8 19 ff ff ff callq 1040 <__cxa_finalize@plt>
1127: e8 64 ff ff ff callq 1090
112c: c6 05 dd 2e 00 00 01 movb $0x1,0x2edd(%rip) # 4010 < TMC_END >
1133: 5d pop %rbp
1134: c3 retq
1135: 0f 1f 00 nopl (%rax)
1138: c3 retq
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 :
frame_dummy():
1140: f3 0f 1e fa endbr64
1144: e9 77 ff ff ff jmpq 10c0
0000000000001149 :
func():
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 8b 05 c1 2e 00 00 mov 0x2ec1(%rip),%eax # 4018
1157: 83 c0 01 add $0x1,%eax
115a: 89 05 b8 2e 00 00 mov %eax,0x2eb8(%rip) # 4018
1160: 8b 05 ae 2e 00 00 mov 0x2eae(%rip),%eax # 4014
1166: 83 c0 01 add $0x1,%eax
1169: 89 05 a5 2e 00 00 mov %eax,0x2ea5(%rip) # 4014
116f: 8b 15 9f 2e 00 00 mov 0x2e9f(%rip),%edx # 4014
1175: 8b 05 9d 2e 00 00 mov 0x2e9d(%rip),%eax # 4018
117b: 89 c6 mov %eax,%esi
117d: 48 8d 3d 80 0e 00 00 lea 0xe80(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
1184: b8 00 00 00 00 mov $0x0,%eax
1189: e8 c2 fe ff ff callq 1050
118e: 90 nop
118f: 5d pop %rbp
1190: c3 retq
0000000000001191 :
util_func():
1191: f3 0f 1e fa endbr64
1195: 55 push %rbp
1196: 48 89 e5 mov %rsp,%rbp
1199: 48 83 ec 10 sub $0x10,%rsp
119d: 89 7d fc mov %edi,-0x4(%rbp)
11a0: 8b 45 fc mov -0x4(%rbp),%eax
11a3: 89 c6 mov %eax,%esi
11a5: 48 8d 3d 72 0e 00 00 lea 0xe72(%rip),%rdi # 201e <_IO_stdin_used+0x1e>
11ac: b8 00 00 00 00 mov $0x0,%eax
11b1: e8 9a fe ff ff callq 1050
11b6: 90 nop
11b7: c9 leaveq
11b8: c3 retq
00000000000011b9 :
main():
11b9: f3 0f 1e fa endbr64
11bd: 55 push %rbp
11be: 48 89 e5 mov %rsp,%rbp
11c1: b8 00 00 00 00 mov $0x0,%eax
11c6: e8 7e ff ff ff callq 1149
11cb: b8 00 00 00 00 mov $0x0,%eax
11d0: e8 74 ff ff ff callq 1149
11d5: b8 00 00 00 00 mov $0x0,%eax
11da: e8 6a ff ff ff callq 1149
11df: bf 0a 00 00 00 mov $0xa,%edi
11e4: e8 a8 ff ff ff callq 1191
11e9: e8 07 00 00 00 callq 11f5
11ee: b8 00 00 00 00 mov $0x0,%eax
11f3: 5d pop %rbp
11f4: c3 retq
00000000000011f5 :
test_func():
11f5: f3 0f 1e fa endbr64
11f9: 55 push %rbp
11fa: 48 89 e5 mov %rsp,%rbp
11fd: 90 nop
11fe: 5d pop %rbp
11ff: c3 retq
0000000000001200 <__libc_csu_init>:
__libc_csu_init():
1200: f3 0f 1e fa endbr64
1204: 41 57 push %r15
1206: 4c 8d 3d ab 2b 00 00 lea 0x2bab(%rip),%r15 # 3db8 <__frame_dummy_init_array_entry>
120d: 41 56 push %r14
120f: 49 89 d6 mov %rdx,%r14
1212: 41 55 push %r13
1214: 49 89 f5 mov %rsi,%r13
1217: 41 54 push %r12
1219: 41 89 fc mov %edi,%r12d
121c: 55 push %rbp
121d: 48 8d 2d 9c 2b 00 00 lea 0x2b9c(%rip),%rbp # 3dc0 <__do_global_dtors_aux_fini_array_entry>
1224: 53 push %rbx
1225: 4c 29 fd sub %r15,%rbp
1228: 48 83 ec 08 sub $0x8,%rsp
122c: e8 cf fd ff ff callq 1000 <_init>
1231: 48 c1 fd 03 sar $0x3,%rbp
1235: 74 1f je 1256 <__libc_csu_init+0x56>
1237: 31 db xor %ebx,%ebx
1239: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1240: 4c 89 f2 mov %r14,%rdx
1243: 4c 89 ee mov %r13,%rsi
1246: 44 89 e7 mov %r12d,%edi
1249: 41 ff 14 df callq *(%r15,%rbx,8)
124d: 48 83 c3 01 add $0x1,%rbx
1251: 48 39 dd cmp %rbx,%rbp
1254: 75 ea jne 1240 <__libc_csu_init+0x40>
1256: 48 83 c4 08 add $0x8,%rsp
125a: 5b pop %rbx
125b: 5d pop %rbp
125c: 41 5c pop %r12
125e: 41 5d pop %r13
1260: 41 5e pop %r14
1262: 41 5f pop %r15
1264: c3 retq
1265: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
126c: 00 00 00 00
0000000000001270 <__libc_csu_fini>:
__libc_csu_fini():
1270: f3 0f 1e fa endbr64
1274: c3 retq
Disassembly of section .fini:
0000000000001278 <_fini>:
_fini():
1278: f3 0f 1e fa endbr64
127c: 48 83 ec 08 sub $0x8,%rsp
1280: 48 83 c4 08 add $0x8,%rsp
1284: c3 retq

老规矩,看不懂全部不重要,找你能看得懂的。

还是找到 l_count 、g_count、util_test 这几个来吧。

ChatGPT

看样子也没啥特殊的,所以问题的关键还在前一步:链接器那里。

综上可知,static的修饰主要是给编译器做了提示,从而生成的obj文件中带了 local属性,这样就告诉链接器,不能扩大这些被local修饰过的内容的使用范围,从而达到了教科书上面说的那样static的作用效果。

其实还有一个方法,就是你写一个带static和不带static的,比如都是全局变量的两个,通过反汇编来对比分析,这样也可以快速看出核心区别,就留给读者去探索吧。

再留一个疑问:编译器、链接器 如何做到一个static修饰的局部变量,只能被定义的当前函数内访问的?

这个问题,有点深入,也留给读者去探索吧。

5 一种绕开static限制的方法
谈到绕开static的限制,其实本文第1小节的那个问题例子就是一个很好的思路和方法。

要想绕开static的限制,无非就是:

将被static修饰的函数转换为没有被static修饰的函数 !

或者

将被static修饰的变量转换为没有被static修饰的变量 !

这个做法,还有一个行业名词叫:封装 。

在第1小节中,代码中演示的是将被static修饰的变量,通过传出其地址,以指针的形式导出给外部模块使用。

下面的代码片段,将展示将被static修饰的函数,转换为没有被static修饰的函数,从而给外部模块使用。

test_a.c

static int test_func_in_a(int a)

int test_func_in_a_ex(int a)

test_b.c
int test_func_in_b(void)
{
int a = 1;
int b;
//call test_func_in_a function fail because of "static"
//b = test_func_in_a(a)
//call test_func_in_a_ex function ok without "static"
b = test_func_in_a_ex(a)
return b;
}

示例代码很简单,仅仅就是在_test_a.c 中,编写一个不被static修饰的函数 test_func_in_a_ex,然后它去调用被static修饰的test_func_in_a函数,这样就轻松绕开了static的限制。

你学会了吗?

6 拓展延伸:如何调用静态库里的被static修饰的函数?

关于这个扩展延伸,其实我很早就起草了一篇博文想写这个内容来着,但是一直拖,一直拖,直到现在还没生产。

索性就在本文做个牵引吧!

个人认为,对于此问题,应该分为两个场景:

静态库的代码,你有修改的权限;或者你能找到人来修改;比如,公司内部不同团队直接的公有库代码;

静态库的代码,你完全没办法查看源码或修改源码;比如一些第三方的库。

针对这两种不同的场景,可能采取的方式是不一样的,你觉得应该如何做最好呢?

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分