Where are global variables located in the elf file - c

I want to learn about elf files, but when I think of global variables, global static variables and scope static variables,
I have some confusion. For example:
int a = 2;
int b;
static int c = 4;
static int d;
void fun(){
static int e = 6;
static int f;
}
int main(void){
fun();
}
Who can tell which segment each variable belongs to? in my opinion,
b, d and f belong to the .bss segment and a,c and e belong to the data segment, but I don't know the difference between global static variables and global variables in elf file.

You can use objdump -t to view the symbol table:
$ objdump -t foo | grep -P ' \b(a|b|c|d|e|f)\b'
0000000000601034 l O .data 0000000000000004 c
0000000000601040 l O .bss 0000000000000004 d
0000000000601044 l O .bss 0000000000000004 f.1710
0000000000601038 l O .data 0000000000000004 e.1709
0000000000601048 g O .bss 0000000000000004 b
0000000000601030 g O .data 0000000000000004 a
You are right that b, d, and f are .bss while a, c, and e are .data. Whether the symbol is static or not is recorded in a separate flag of the symbol table—that’s the l or g flag in the second column.
The elf(5) man page says that these are recorded using the STB_LOCAL and STB_GLOBAL values for the st_info member of the symbol table. /usr/include/elf.h says that STB_GLOBAL is 1, while STB_LOCAL is 0. There is a macro ST_BIND to retrieve the binding bits of the st_info field.
There are tons of other flags for objdump—see the man page. objdump works with all architectures, but there is also an elfdump tool that does a bit better job of showing elf-specific stuff. objdump and the underlying BFD library can do a bad job of showing some file-format-specific data.

In general, the data segment of the executable contains initialized global/static variables and the BSS segment contains uninitialized global/static variables.
When the loader loads your program into memory, the unitialized global/static variables are automatically zero-filled.
In C, static variables (initialized or not) inside a function just mean the variables have local/function scope (sometimes referred to as internal static), but they still live in the Data/BSS segments depending on whether or not they are initialized.
So regardless of how many times fun() gets called, the static variables are initilized only once when the program is loaded.
Variables defined as static and outside any functions still live in either the data or bss segments, but have file scope only.
When your code is compiled, there is an import and export list that is part of each object file and is used by the linkage editor. Your static variables will not be in the export list and therefore inaccessable to other object files.
By excluding the static keyword, your global variables are placed in the export list and can be referred to by other object modules and the linkage editor will be able to find the symbols when creating the executable.
For a pictoral view:
+--------- TEXT ---------+ Low memory
| main() |
| fun() |
+--------- DATA ---------+
| int a (global scope) |
| int c (file scope) |
| int e (function scope) |
+---------- BSS ---------+
| int b (global scope) |
| int d (file scope) |
| int f (function scope) |
+------------------------+

Related

How to determine the address range of global variables in a shared library at runtime?

At runtime, are global variables in a loaded shared library guaranteed to occupy a contiguous memory region? If so, is it possible to find out that address range?
Context: we want to have multiple "instances" of a shared library (e.g. a protocol stack implementation) in memory for simulation purposes (e.g. to simulate a network with multiple hosts/routers). One of the approaches we are trying is to load the library only once, but emulate additional instances by creating and maintaining "shadow" sets of global variables, and switch between instances by memcpy()'ing the appropriate shadow set in/out of the memory area occupied by the global variables of the library. (Alternative approaches like using dlmopen() to load the library multiple times, or introducing indirection inside the shared lib to access global vars have their limitations and difficulties too.)
Things we tried:
Using dl_iterate_phdr() to find the data segment of the shared lib. The resulting address range was not too useful, because (1) it did not point to an area containing the actual global variables but to the segment as loaded from the ELF file (in readonly memory), and (2) it contained not only the global vars but also additional internal data structures.
Added start/end guard variables in C to the library code, and ensured (via linker script) that they are placed at the start and end of the .data section in the shared object. (We verified that with objdump -t.) The idea was that at runtime, all global variables would be located in the address range between the two guard variables. However, our observation was that the relative order of the actual variables in memory was quite different than what would follow from the addresses in the shared object. A typical output was:
$ objdump -t libx.so | grep '\.data'
0000000000601020 l d .data 0000000000000000 .data
0000000000601020 l O .data 0000000000000000 __dso_handle
0000000000601038 l O .data 0000000000000000 __TMC_END__
0000000000601030 g O .data 0000000000000004 custom_data_end_marker
0000000000601028 g O .data 0000000000000004 custom_data_begin_marker
0000000000601034 g .data 0000000000000000 _edata
000000000060102c g O .data 0000000000000004 global_var
$ ./prog
# output from dl_iterate_phdr()
name=./libx.so (7 segments)
header 0: type=1 flags=5 start=0x7fab69fb0000 end=0x7fab69fb07ac size=1964
header 1: type=1 flags=6 start=0x7fab6a1b0e08 end=0x7fab6a1b1038 size=560 <--- data segment
header 2: type=2 flags=6 start=0x7fab6a1b0e18 end=0x7fab6a1b0fd8 size=448
header 3: type=4 flags=4 start=0x7fab69fb01c8 end=0x7fab69fb01ec size=36
header 4: type=1685382480 flags=4 start=0x7fab69fb0708 end=0x7fab69fb072c size=36
header 5: type=1685382481 flags=6 start=0x7fab69bb0000 end=0x7fab69bb0000 size=0
header 6: type=1685382482 flags=4 start=0x7fab6a1b0e08 end=0x7fab6a1b1000 size=504
# addresses obtained via dlsym() are consistent with the objdump output:
dlsym('custom_data_begin_marker') = 0x7fab6a1b1028
dlsym('custom_data_end_marker') = 0x7fab6a1b1030 <-- between the begin and end markers
# actual addresses: at completely different address range, AND in completely different order!
&custom_data_begin_marker = 0x55d613f8e018
&custom_data_end_marker = 0x55d613f8e010 <-- end marker precedes begin marker!
&global_var = 0x55d613f8e01c <-- after both markers!
Which means the "guard variables" approach does not work.
Maybe we should iterate over the Global Offset Table (GOT) and collect the addresses of global variables from there? However, there doesn't seem to be an official way for doing that, if it's possible at all.
Is there something we overlooked? I'll be happy to clarify or post our test code if needed.
EDIT: To clarify, the shared library in question is a 3rd party library whose source code we prefer not to modify, hence the quest for the above general solution.
EDIT2: As further clarification, the following code outlines what I would like to be able to do:
// x.c -- source for the shared library
#include <stdio.h>
int global_var = 10;
void bar() {
global_var++;
printf("global_var=%d\n", global_var);
}
// a.c -- main program
#include <stdlib.h>
#include <dlfcn.h>
#include <memory.h>
struct memrange {
void *ptr;
size_t size;
};
extern int global_var;
void bar();
struct memrange query_globals_address_range(const char *so_file)
{
struct memrange result;
// TODO what generic solution can we use here instead of the next two specific lines?
result.ptr = &global_var;
result.size = sizeof(int);
return result;
}
struct memrange g_range;
void *allocGlobals()
{
// allocate shadow set and initialize it with actual global vars
void *globals = malloc(g_range.size);
memcpy(globals, g_range.ptr, g_range.size);
return globals;
}
void callBar(void *globals) {
memcpy(g_range.ptr, globals, g_range.size); // overwrite globals from shadow set
bar();
memcpy(globals, g_range.ptr, g_range.size); // save changes into shadow set
}
int main(int argc, char *argv[])
{
g_range = query_globals_address_range("./libx.so");
// allocate two shadow sets of global vars
void *globals1 = allocGlobals();
void *globals2 = allocGlobals();
// call bar() in the library with a few times with each
callBar(globals1);
callBar(globals2);
callBar(globals2);
callBar(globals1);
callBar(globals1);
return 0;
}
Build+run script:
#! /bin/sh
gcc -c -g -fPIC x.c -shared -o libx.so
gcc a.c -g -L. -lx -ldl -o prog
LD_LIBRARY_PATH=. ./prog
EDIT3: Added dl_iterate_phdr() output
Shared libraries are compiled as Position-Independent Code. That means that unlike executables, addresses are not fixed, but are rather decided during dynamic linkage.
From a software engineering standpoint, the best approach is to use objects (structs) to represent all your data and avoid global variables (such data structures are typically called "contexts"). All API functions then take a context argument, which allows you to have multiple contexts in the same process.
At runtime, are global variables in a loaded shared library guaranteed to occupy a contiguous memory region?
Yes: on any ELF platform (such as Linux) all writable globals are typically grouped into a single writable PT_LOAD segment, and that segment is located at a fixed address (determined at the library load time).
If so, is it possible to find out that address range?
Certainly. You can find the library load address using dl_iterate_phdr, and iterate over the program segments that it gives you. One of the program headers will have .p_type == PT_LOAD, .p_flags == PF_R|PF_W. The address range you want is [dlpi_addr + phdr->p_vaddr, dlpi_addr + phdr->p_vaddr + phdr->p_memsz).
Here:
# actual addresses: completely different order:
you are actually looking at the address of the GOT entries in the main executable, and not the addresses of the variables themselves.

What is the first column of nm output?

Thats my code:
int const const_global_init = 2;
int const const_global;
int global_init = 4;
int global;
static int static_global_init = 3;
static int static_global;
static int static_function(){
return 2;
}
double function_with_param(int a){
static int static_local_init = 3;
static int static_local;
return 2.2;
}
int main(){
}
I generate main.o and i try to understood nm output. After i use nm main.o --printfile-name -a i get this output:
main.o:0000000000000000 b .bss
main.o:0000000000000000 n .comment
main.o:0000000000000004 C const_global
main.o:0000000000000000 R const_global_init
main.o:0000000000000000 d .data
main.o:0000000000000000 r .eh_frame
main.o:000000000000000b T function_with_param
main.o:0000000000000004 C global
main.o:0000000000000000 D global_init
main.o:0000000000000027 T main
main.o:0000000000000000 a main.c
main.o:0000000000000000 n .note.GNU-stack
main.o:0000000000000000 r .rodata
main.o:0000000000000000 t static_function
main.o:0000000000000000 b static_global
main.o:0000000000000004 d static_global_init
main.o:0000000000000004 b static_local.1733
main.o:0000000000000008 d static_local_init.1732
main.o:0000000000000000 t .text
I understood 2nd and 3rd column but, i really dont know what is in the first column, whether it is the address or size? I know somethink about .bbs, .comment, .data and .text segments but what is it .eh_frame, .note.GNU-stack and .rodata?
... i really dont know what is in the first column, whether it is the address or size?
My local manpage (from man nm) says
DESCRIPTION
GNU nm lists the symbols from object files objfile.... If no object files are listed as arguments, nm assumes the file a.out.
For each symbol, nm shows:
· The symbol value, in the radix selected by options (see below), or hexadecimal by default.
that is, the first column is the 'value' of the symbol. To understand what that means, it's helpful to know something about ELF and the runtime linker, but in general it will simply be an offset into the relevant section.
Understanding something about ELF will also help with the other points: man elf tells us that the .rodata section is read-only data (that is: constant values hardcoded into the program that never change. String literals might go here).
.eh_frame is used for exception-handling and other call-stack-frame metadata (a search for eh_frame has this question as the first hit).

How do I see the memory locations of static variables within .bss?

Supposing I have a static variable declared in gps_anetenova_m10478.c as follows:
static app_timer_id_t m_gps_response_timeout_timer_id;
I have some sort of buffer overrun bug in my code and at some point a write to the variable right before m_gps_response_timeout_timer_id in memory is overwriting it.
I can find out where m_gps_response_timeout_timer_id is in memory using the 'Expressions' view in Eclipse's GDB client. Just enter &m_gps_response_timeout_timer_id. But how do I tell which variable is immediately before it in memory?
Is there a way to get this info into the .map file that ld produces? At the moment I only see source files:
.bss 0x000000002000011c 0x0 _build/debug_leds.o
.bss 0x000000002000011c 0x11f8 _build/gps_antenova_m10478.o
.bss 0x0000000020001314 0x161c _build/gsm_ublox_sara.o
I'll be honest, I don't know enough about Eclipse to give an easy way within Eclipse to get this. The tool you're probably looking for is either objdump or nm. An example with objdump is to simply run objdump -x <myELF>. This will then return all symbols in the file, which section they're in, and their addresses. You'll then have to manually search for the variable in which you're interested based on the addresses.
objdump -x <ELFfile> will give output along the lines of the following:
000120d8 g F .text 0000033c bit_string_copy
00015ea4 g O .bss 00000004 overflow_bit
00015e24 g .bss 00000000 __bss_start
00011ce4 g F .text 0000003c main
00014b6c g F .text 0000008c integer_and
The first column is the address, the fourth the section and the fifth the length of that field.
nm <ELFfile> gives the following:
00015ea8 B __bss_end
00015e24 B __bss_start
0000c000 T _start
00015e20 D zero_constant
00015e24 b zero_constant_itself
The first column is the address and the second the section. D/d is data, B/b is BSS and T/t is text. The rest can be found in the manpage. nm also accepts the -n flag to sort the lines by their numeric address.

Location of global variables with DWARF (and relocation)

When dynamically linking a binary with libraries, relocation information is used to bind the variables/functions of the different ELF objects. However DWARF is not affected by relocation: how is a debugger supposed to resolve global variables?
Let's say I have liba.so (a.c) defining a global variable (using GNU/Linux with GCC or Clang):
#include <stdio.h>
int foo = 10;
int test(void) {
printf("&foo=%p\n", &foo);
}
and an program b linked against liba.so (b.c):
#include <stdio.h>
extern int foo;
int main(int argc, char** argv) {
test();
printf("&foo=%p\n", &foo);
return 0;
}
I expect that "foo" will be instanciated in liba.so
but in fact it is instanciated in both liba.so and b:
$ ./b
&foo=0x600c68 # <- b .bss
&foo=0x600c68 # <- b .bss
The foo variable which is used (both by b and by lib.so) is in the .bss of b
and not in liba.so:
[...]
0x0000000000600c68 - 0x0000000000600c70 is .bss
[...]
0x00007ffff7dda9c8 - 0x00007ffff7dda9d4 is .data in /home/foo/bar/liba.so
0x00007ffff7dda9d4 - 0x00007ffff7dda9d8 is .bss in /home/foo/bar/liba.so
The foo variable is instanciated twice:
once in liba.so (this instance is not used when linked with program b)
once in b (this instance is used instance of the other in b).
(I don't really understand why the variable is instanciated in the executable.)
There is only a declaration in b (as expected) in the DWARF informations:
$ readelf -wi b
[...]
<1><ca>: Abbrev Number: 9 (DW_TAG_variable)
<cb> DW_AT_name : foo
<cf> DW_AT_decl_file : 1
<d0> DW_AT_decl_line : 3
<d1> DW_AT_type : <0x57>
<d5> DW_AT_external : 1
<d5> DW_AT_declaration : 1
[...]
and a location is found in liba.so:
$ readelf -wi liba.so
[...]
<1><90>: Abbrev Number: 5 (DW_TAG_variable)
<91> DW_AT_name : foo
<95> DW_AT_decl_file : 1
<96> DW_AT_decl_line : 3
<97> DW_AT_type : <0x57>
<9b> DW_AT_external : 1
<9b> DW_AT_location : 9 bloc d'octets: 3 d0 9 20 0 0 0 0 0 (DW_OP_addr: 2009d0)
[...]
This address is the location of the (unsued) instance of foo in liba.so (.data).
I end up with 2 instances of the foo global variable (on in liba.so and one in b);
only the first one can be seen with DWARF;
only the secone one is used.
How is the debugger supposed to resolve the foo global variable?
I don't really understand why the variable is instanciated in the executable.
You can find the answer here.
How is the debugger supposed to resolve the foo global variable
The debugger reads symbol tables (in addition to debug info), and foo does get defined in both the main executable b, and in liba.so:
nm b | grep foo
0000000000600c68 B foo
(I read the Oracle doc provided by #Employed Russian.)
The global variable reinstanciation is done for non-PIC code in order to dereference the variable in a non-PIC way without patching the non-PIC code:
a copy of the variable is done for non-PIC code;
the variable is instanciated in the executable;
a copy relocation instruction is used to copy the data from the source shared objet at dynamic linking time;
the instance in the shared objet is not used (after the relocation copy has been done).
Copy relocation instructions:
$readelf -r b
Relocation section '.rela.dyn' at offset 0x638 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600c58 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000600ca8 001200000005 R_X86_64_COPY 0000000000600ca8 foo + 0
For functions, the GOT+PLT technique is used the same way they are used in PIC code.

Replacing static function in kernel module

Folks,
I'm trying to hack a kernel module by modifying its symbol. The basic idea is to replace the original function with new function by overwriting its address in the symtab. However, I found when declaring the function as static, the hacking fails. But it works with non-static function. My example code is below:
filename: orig.c
int fun(void) {
printk(KERN_ALERT "calling fun!\n");
return 0;
}
int evil(void) {
printk(KERN_ALERT "===== EVIL ====\n");
return 0;
}
static int init(void) {
printk(KERN_ALERT "Init Original!");
fun();
return 0;
}
void clean(void) {
printk(KERN_ALERT "Exit Original!");
return;
}
module_init(init);
module_exit(clean);
Then I follow the styx's article to replace the original function "fun" in symtab to call function "evil", http://www.phrack.org/issues.html?issue=68&id=11
>objdump -t orig.ko
...
000000000000001b g F .text 000000000000001b evil
0000000000000056 g F .text 0000000000000019 cleanup_module
0000000000000036 g F .text 0000000000000020 init_module
0000000000000000 g F .text 000000000000001b fun
...
By executing the elfchger
>./elfchger -s fun -v 1b orig.ko
[+] Opening orig.ko file...
[+] Reading Elf header...
>> Done!
[+] Finding ".symtab" section...
>> Found at 0xc630
[+] Finding ".strtab" section...
>> Found at 0xc670
[+] Getting symbol' infos:
>> Symbol found at 0x159f8
>> Index in symbol table: 0x1d
[+] Replacing 0x00000000 with 0x0000001b... done!
I can successfully change the fun's symbol table to be equal to evil and inserting the module see the effects:
000000000000001b g F .text 000000000000001b evil
...
000000000000001b g F .text 000000000000001b fun
> insmod ./orig.ko
> dmesg
[ 7687.797211] Init Original!
[ 7687.797215] ===== EVIL ====
While this works fine. When I change the declaration of fun to be "static int fun(void)" and follows the same steps as mentioned above, I found the evil does not get called. Could anyone give me some suggestion?
Thanks,
William
Short version: Declaring a function as 'static' makes it local and prevents the symbol to be exported. Thus, the call is linked statically, and the dynamic linker does not effect the call in any way at load time.
Long Version
Declaring a symbol as 'static' prevents the compiler from exporting the symbol, making it local instead of global. You can verify this by looking for the (missing) 'g' in your objdump output, or at the lower-case 't' (instead of 'T') in the output of 'nm'. The compiler might also inline the local function, in which case the symbol table wouldn't contain it at all.
Local symbols have to be unique only for the translation unit in which they are defined. If your module consisted of multiple translation units, you could have a static fun() in each of them. An nm or objdump of the finished .ko may then contain multiple local symbols called fun.
This also implies that local symbols are valid only in their respective translation unit, and also can be referred (in your case: called) only from inside this unit. Otherwise, the linker just would not now, which one you mean. Thus, the call to static fun() is already linked at compile time, before the module is loaded.
At load time, the dynamic linker won't tamper with the local symbol fun or references (in particular: calls) to it, since:
its local linkage already done
there are potentially more symbols named 'fun' throughout and the dynamic linker would not be able to tell, which one you meant

Resources