Static Function vs Non-Static in Static Linking? - c

I wrote the following 2 programs using C:
First:
int foo(int x)
{
return 1;
}
int main()
{
return foo(4);
}
Second:
static int foo(int x)
{
return 1;
}
int main()
{
return foo(4);
}
Then I ran:
gcc -c my_file.c
For the first file I saw (Not full output):
000000000000000e <main>:
e: 55 push %rbp
f: 48 89 e5 mov %rsp,%rbp
12: bf 04 00 00 00 mov $0x4,%edi
17: e8 00 00 00 00 callq 1c <main+0xe>
1c: 5d pop %rbp
1d: c3 retq
And for the second:
000000000000000e <main>:
e: 55 push %rbp
f: 48 89 e5 mov %rsp,%rbp
12: bf 04 00 00 00 mov $0x4,%edi
17: e8 e4 ff ff ff callq 0 <foo>
1c: 5d pop %rbp
1d: c3 retq
My question is, why in the first file we needed relocation when the function is defined (and not only declared) in the current file? This sounds too strange to me.

You look at unresolved code.
The first version makes foo() global, and therefore there are entries in appropriate tables, symbols and relocations, not shown in the listing. <Edit>Most probably because the compiler works that way, when it emits a call to a global function, it puts zeroes (or anything else) in the address field. It does not matter that this global function is in the same translation unit. Called with other options or other versions of the compiler or other compilers might yield a different result.</Edit>
In the second version the compiler knows that foo() is local and resolves the call instantly without the need to generate relocation entries.
The calls will be resolved to equal values if you link the program.
<Edit>Interesting: I tried to reproduce this with GCC 8.1.0 (MinGW-W64) on Windows, and both calls are resolved by the compiler. However, with GCC 11.1.0 of the current Manjaro Linux, it shows the described behaviour.</Edit>

Related

In shared objects, why does gcc relocate through the GOT for global variables which are defined in the same shared object?

While answering another question on Stack, I came upon a question myself. With the following code in shared.c:
#include "stdio.h"
int glob_var = 0;
void shared_func(){
printf("Hello World!");
glob_var = 3;
}
Compiled with:
gcc -shared -fPIC shared.c -oshared.so
If I disassemble the code with objdump -D shared.so, I get the following for shared_func:
0000000000001119 <shared_func>:
1119: f3 0f 1e fa endbr64
111d: 55 push %rbp
111e: 48 89 e5 mov %rsp,%rbp
1121: 48 8d 3d d8 0e 00 00 lea 0xed8(%rip),%rdi # 2000 <_fini+0xebc>
1128: b8 00 00 00 00 mov $0x0,%eax
112d: e8 1e ff ff ff callq 1050 <printf#plt>
1132: 48 8b 05 9f 2e 00 00 mov 0x2e9f(%rip),%rax # 3fd8 <glob_var##Base-0x54>
1139: c7 00 03 00 00 00 movl $0x3,(%rax)
113f: 90 nop
1140: 5d pop %rbp
1141: c3 retq
Using readelf -S shared.so, I get (for the GOT):
[22] .got PROGBITS 0000000000003fd8 00002fd8
0000000000000028 0000000000000008 WA 0 0 8
Correct me if I'm wrong but, looking at this, the relocation for accessing glob_var seems to be through the GOT. As I read on some websites, this is due to limitations in x86-64 machine code where RIP-relative addressing is limited to a 32 bits offset. This explanation is not satisfactory to me because, since the global variable is in the same shared object, then it is guaranteed to be found in its own data segment. The global variable could thus be accessed using RIP-relative addressing without an issue.
I would understand the GOT relocation if glob_var had been declared extern but, in this case, it is defined in the same shared object. Why does gcc require a relocation through the GOT? Is it because it is not able to detect that the global variable is defined within the same shared object?
Related: Why are nonstatic global variables defined in shared objects referenced using GOT?
The above is 11 years old and doesn't answer my question because there doesn't seem to be an appropriate answer there.
Bonus: what does <glob_var##Base-0x54> mean in the disassembly of shared_func? Why it isn't <glob_var#GOT>?
Thanks for any help!

C/C++ volatile variable accessed from another module

I know and understand the purpose of volatile variables and optimisation in general (well, I think I do!). This question relates specifically to what happens if a variable is accessed outside the module it is declared in.
In the following scenario, if funcThatWaits was called inside bar.c, it could be optimised and not fetch the value of sTheVar each loop iteration.
However, when GetTheVar is called externally could the same optimisation apply or does the function call ensure sTheVar will always be read each loop iteration?
I am not suggesting this is good code or practice, but an example for the sake of the question.
bar.h
int GetTheVar(void);
bar.c
static /*volatile*/ int sTheVar;
int GetTheVar(void)
{
return sTheVar;
}
static void someISROrFuncCalledFromAnotherThread(void)
{
sTheVar = 1;
}
foo.c
#include "bar.h"
void funcThatWaits(void)
{
while(GetTheVar() != 1) {}
}
when GetTheVar is called externally could the same optimisation apply or does the function call ensure sTheVar will always be read each loop iteration?
The same optimization may apply. For instance, if you are using LTO (Link-Time Optimization), then the compiler knows everything about GetTheVar and will likely decide funcThatWaits is an infinite loop (which, by the way, would be UB).
Function calls are not going to be optimized away since, for all the caller knows, the function being called could depend on some exogenous state.
I compiled the following three files using gcc:
foo.c
#include "bar.h"
void funcThatWaits(void) {
while ( getVar() != 1 );
}
bar.c
#include "foo.h"
static int theVar;
int getTheVar(void) {
return theVar;
}
void theFunc(void) {
funcThatWaits();
}
test.c
#include "bar.h"
int main() {
theFunc();
return 0;
}
Compiling those three into a.out and running objdump -d a.out, the following comes out:
00000000000005fa <main>:
5fa: 55 push %rbp
5fb: 48 89 e5 mov %rsp,%rbp
5fe: e8 25 00 00 00 callq 628 <theFunc>
603: b8 00 00 00 00 mov $0x0,%eax
608: 5d pop %rbp
609: c3 retq
000000000000060a <funcThatWaits>:
60a: 55 push %rbp
60b: 48 89 e5 mov %rsp,%rbp
60e: 90 nop
60f: e8 08 00 00 00 callq 61c <getTheVar>
614: 83 f8 01 cmp $0x1,%eax
617: 75 f6 jne 60f <funcThatWaits+0x5>
619: 90 nop
61a: 5d pop %rbp
61b: c3 retq
000000000000061c <getTheVar>:
61c: 55 push %rbp
61d: 48 89 e5 mov %rsp,%rbp
620: 8b 05 ee 09 20 00 mov 0x2009ee(%rip),%eax # 201014 <theVar>
626: 5d pop %rbp
627: c3 retq
0000000000000628 <theFunc>:
628: 55 push %rbp
629: 48 89 e5 mov %rsp,%rbp
62c: e8 d9 ff ff ff callq 60a <funcThatWaits>
631: 90 nop
632: 5d pop %rbp
633: c3 retq
634: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
63b: 00 00 00
63e: 66 90 xchg %ax,%ax

i386-elf-gcc out put strange assembler command about "static a = 0"

i am write a mini os. And when i write this code to show time clock, its goes wrong
7 void timer_callback(pt_regs *regs)
8 {
9 static uint32_t tick = 0;
10 printf("Tick: %dtimes\n", tick);
11 tick++;
12 }
tick is initialise not with 0, but 1818389861. but if tick init with 0x01 or anything else zero, it's ok!!!
so i wirte a simple c file then objdump:
staic.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
extern void printf(char *, int);
int main(){
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
static int a = 1;
printf("%d\n", a);
11: a1 00 00 00 00 mov 0x0,%eax
16: 83 ec 08 sub $0x8,%esp
19: 50 push %eax
1a: 68 00 00 00 00 push $0x0
1f: e8 fc ff ff ff call 20 <main+0x20>
24: 83 c4 10 add $0x10,%esp
return 0;
27: b8 00 00 00 00 mov $0x0,%eax
}
2c: 8b 4d fc mov -0x4(%ebp),%ecx
2f: c9 leave
30: 8d 61 fc lea -0x4(%ecx),%esp
33: c3 ret
so strange, no memory used!!!
Update: let me say it clearly
the second static.c is an experiment, it was thought it show no memory used, but i was wrong, mov 0x0 %eab is. i confuse 0x0 and $0x0 /..\
my origin problem is why tick not succeed init with 0.(but can init with 1 or anyelsenumber).
i look up it again use gdb, ok, it do use memory like mov
eax,ds:0x106010,but the real strong thing is the memory x 0x106010 is not 0,but it should be, just as i said, if i let tick = 1 or anythingelse, memory do init as i want, that is the strange thing!
the tool: gdb ,objdump return different asm(different means,not formate),because, just learn os,not good at c, so i let it go,ignore it....
Memory is used, be sure of that; however, you won't find that memory in the .text section. Memory for static variables is allocated in either .bss (when zero-initialized; or, in case of C++, dynamically initialized) or .data (when non-zero initialized) section.
When dumping object files with objdump using the -d (disassembly) option, it is important to also use the -r (relocations) option. Without that, the disassembly you get is deceiving and makes little sense.
In your case, the instruction at addresses 11 and 1f must have relocations, at address 11, to the variable a and at address 1f, to the function printf. The instruction at address 11 loads the value from your variable a, without proper relocations it looks as if it loaded a value from address 0.
As to your original question, the value you get, 1818389861, or 0x6C626D65, is quite remarkable. I would bet that somewhere in your program you have a buffer overrun involving a string containing the subsequence embl.
As a side note, I would like to call your attention to the use of correct type specifications in printf calls. The type specification %d corresponds to the type int; on all modern mainstream architectures, int and int32_t are of the same size. However, that is not guaranteed to always be so. There are special type specifications for use with explicitly-sized types, for example, for an int32_t you use "PRId32":
uint32_t x;
printf("%"PRId32, x);

C: Var and Function have same name -- a bug of ld?

In my project (https://github.com/zzt93/os-lab1), I encounter that a global variable has the same name with a function, but compile it produce on error or warning, which cause a bug.
A simple program which can almost reproduce this problem:
//a.c
struct {
int t;
int *s;
} empty, full;
int main(){
printf("full is at %p", &full);
printf("empty is at %p", &empty);
empty.t = 1;
return 0;
}
//b.c
int empty() {
return 1;
}
Compiling them use gcc -o res.out -Wall -g -Wextra a.c b.c
will just produce some warning like this (notice: in my project, it even produce no error):
/usr/bin/ld: Warning: alignment 1 of symbol empty in /tmp/ccq70SCM.o is smaller than 16 in /tmp/ccVCOeWq.o
/usr/bin/ld: Warning: size of symbol empty changed from 16 in /tmp/ccVCOeWq.o to 11 in /tmp/ccq70SCM.o
/usr/bin/ld: Warning: type of symbol empty changed from 1 to 2 in /tmp/ccq70SCM.o
it seems it take struct empty and function empty as the same one.
Decompile it, you can clearly see that linker link the address of function empty rather than that struct empty.So try running res.out will cause segment fault.
40054e: be 73 05 40 00 mov $0x400573,%esi
400553: bf 12 06 40 00 mov $0x400612,%edi
400558: b8 00 00 00 00 mov $0x0,%eax
40055d: e8 ae fe ff ff callq 400410 <printf#plt>
400562: c7 05 07 00 00 00 01 movl $0x1,0x7(%rip) # 400573 <empty>
400569: 00 00 00
40056c: b8 00 00 00 00 mov $0x0,%eax
400571: 5d pop %rbp
400572: c3 retq
0000000000400573 <empty>:
400573: 55 push %rbp
400574: 48 89 e5 mov %rsp,%rbp
400577: b8 01 00 00 00 mov $0x1,%eax
40057c: 5d pop %rbp
40057d: c3 retq
40057e: 66 90 xchg %ax,%ax
Question:
Why linker choose function rather than that struct? Am i right to think it as a bug?
why add a static for the declaration of struct can prevent this error? -- I understand that static make the variable invisible outside this file, but notice I add static to struct empty not function empty solving the problem.
Edit::
And strange enough, in the symbol table of res.out, there is only one empty
Name Value Class Type Size Line Section
empty |0000000000400573| T | FUNC|000000000000000b| |.text
I am using
gcc version 4.9.2
Adding static prevents the error because static, when applied to functions or global variables, makes the symbol not be exported to the linker - in simple words, it makes it "private" to that file.
If you don't use static, the linker will see both definitions, but the types don't match. However, since compilation is applied file by file, the linker has no way to know the correct type of a variable - it must trust that you did your job and didn't lie.
This is why header files are important - it makes sure that types match in different files.

gcc function parameter alignment on stack frame

I have this test.c on my Ubuntu14.04 x86_64 system.
void foo(int a, long b, int c) {
}
int main() {
foo(0x1, 0x2, 0x3);
}
I compiled this with gcc --no-stack-protector -g test.c -o test and got the assembly code with objdump -dS test -j .text
00000000004004ed <_Z3fooili>:
void foo(int a, long b, int c) {
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 89 7d fc mov %edi,-0x4(%rbp)
4004f4: 48 89 75 f0 mov %rsi,-0x10(%rbp)
4004f8: 89 55 f8 mov %edx,-0x8(%rbp) // !!Attention here!!
}
4004fb: 5d pop %rbp
4004fc: c3 retq
00000000004004fd <main>:
int main() {
4004fd: 55 push %rbp
4004fe: 48 89 e5 mov %rsp,%rbp
foo(0x1, 0x2, 0x3);
400501: ba 03 00 00 00 mov $0x3,%edx
400506: be 02 00 00 00 mov $0x2,%esi
40050b: bf 01 00 00 00 mov $0x1,%edi
400510: e8 d8 ff ff ff callq 4004ed <_Z3fooili>
}
400515: b8 00 00 00 00 mov $0x0,%eax
40051a: 5d pop %rbp
40051b: c3 retq
40051c: 0f 1f 40 00 nopl 0x0(%rax)
I know that the function parameters should be pushed to stack from right to left in sequence. So I was expecting this
void foo(int a, long b, int c) {
push %rbp
mov %rsp,%rbp
mov %edi,-0x4(%rbp)
mov %rsi,-0x10(%rbp)
mov %edx,-0x14(%rbp) // c should be push on stack after b, not after a
But gcc seemed clever enough to push parameter c(0x3) right after a(0x1) to save the four bytes which should be reserved for data alignment of b(0x2). Can someone please explain this and show me some documentation on why gcc did this?
The parameters are passed in registers - edi, esi, edx (then rcx, r8, r9 and only then pushed on stack) - just what the Linux amd64 calling convention mandates.
What you see in your function is just how the compiler saves them upon entry when compiling with -O0, so they're in memory where a debugger can modify them. It is free to do it in any way it wants, and it cleverly does this space optimization.
The only reason it does this is that gcc -O0 always spills/reloads all C variables between C statements to support modifying variables and jumping between lines in a function with a debugger.
All this would be optimized out in release build in the end.

Resources