Repository: angr/angr

Issue:
Symbolic execution / init state does not run static constructor
<!--
*Disclaimer:
The angr suite is maintained by a small team of volunteers.
While we cannot guarantee any timeliness for fixes and enhancements, we will do our best.
For more real-time help with angr, from us and the community, join our [Slack.](https://angr.io/invite/)*
-->
---

**Describe the bug.**
<!--
Please include a clear and concise description of what the bug is.
-->
Instructions in the constructor of a static object aren't run when starting a symbolic execution from an `entry_state` or a `full_init_state`.

**Environment Information.**
<!--
Many common issues are caused by problems with the local Python environment.
Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date.
Please include the output of `python -m angr.misc.bug_report` here.
-->
~~My "installation" of angr is a bit funny, however I can't manage to install the git version properly, and the pip version does not work for gcc 11.2 binaries yet. I hope this doesn't cause the current bug, but please try to reproduce it the way below with a proper installation.~~
Update: I tried this with a proper installation of the current git version with the same results.

**To Reproduce.**
<!--
Please include *both a script to reproduce the crash, and attach the binary used, if possible*
-->
test2.cc - c++ source of the binary `test2` used:
```c++
#include <stdio.h>

int glob;
int glob2 = 8;

class liba {
public:
    liba() {
        printf("static out\n");
        x = 3;
        glob = 6;
    }
    static int x;
    static liba instance;
};

int liba::x;
liba liba::instance;

int main() {
    printf("main out\nstatic member: %d\nglob: %d\nglob2: %d\n", liba::x, glob, glob2);
}
```

test2.py - angr script
```py
import angr

p = angr.Project('test2')
es = p.factory.entry_state()
simgr = p.factory.simgr(es)

simgr.run()

print(simgr.one_deadended.posix.dumps(1).decode('utf8'))
```

These sources and the binary: [test2.zip](https://github.com/angr/angr/files/8377743/test2.zip)

output of the normally executed binary:
```
static out
main out
static member: 3
glob: 6
glob2: 8
```

output of the angr script:
```
WARNING | 2022-03-29 11:47:08,448 | cle.backends.tls | The provided object has an invalid tls_data_size. Skip TLS loading.
WARNING | 2022-03-29 11:47:09,890 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior.
WARNING | 2022-03-29 11:47:09,890 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2022-03-29 11:47:09,890 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2022-03-29 11:47:09,890 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2022-03-29 11:47:09,890 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2022-03-29 11:47:09,890 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff9c with 4 unconstrained bytes referenced from 0x401059 (_start+0x9 in test2 (0x401059))
main out
static member: 0
glob: 0
glob2: 8
```

I've tried using a `full_init_state` with the exact same result.


Discussion:
oh god damn it

it looks like glibc changed the protocol for how the dynamic linker parses the initializers in glibc 2.34. If I compile your source on my ubuntu 20.04 system, everything works fine. See https://sourceware.org/bugzilla/show_bug.cgi?id=23323.

This will be a pain to fix.

Resolving patch:
diff --git a/angr/procedures/glibc/__libc_start_main.py b/angr/procedures/glibc/__libc_start_main.py
--- a/angr/procedures/glibc/__libc_start_main.py
+++ b/angr/procedures/glibc/__libc_start_main.py
@@ -1,6 +1,7 @@
-
 import logging
 
+from cle import AT
+
 import angr
 
 l = logging.getLogger(name=__name__)
@@ -9,11 +10,11 @@
 # __libc_start_main
 ######################################
 class __libc_start_main(angr.SimProcedure):
-    #pylint:disable=arguments-differ,unused-argument,attribute-defined-outside-init
+    #pylint:disable=arguments-differ,unused-argument,attribute-defined-outside-init,missing-class-docstring
 
     ADDS_EXITS = True
     NO_RET = True
-    local_vars = ('main', 'argc', 'argv', 'init', 'fini')
+    local_vars = ('main', 'argc', 'argv', 'init', 'fini', 'initializers')
 
     def _initialize_b_loc_table(self):
         """
@@ -135,10 +136,26 @@ def run(self, main, argc, argv, init, fini):
         # TODO: __cxa_atexit calls for various at-exit needs
 
         if not self.state.solver.is_true(self.init == 0):
+            self.initializers = None
             self.call(self.init, (self.argc[31:0], self.argv, self.envp), 'after_init',
                 prototype = 'int main(int argc, char **argv, char **envp)')
         else:
+            obj = self.project.loader.main_object
+            init_func = getattr(obj, '_init_func', None)
+            init_arr = getattr(obj, '_init_arr', None)
+            init_func = [init_func] if init_func else []
+            self.initializers = init_func + list(init_arr)
+            for i, x in enumerate(self.initializers):
+                self.initializers[i] = AT.from_lva(x, obj).to_mva()
+            self.inside_init(main, argc, argv, init, fini)
+
+    def inside_init(self, main, argc, argv, init, fini):
+        if len(self.initializers) == 0:
             self.after_init(main, argc, argv, init, fini)
+        else:
+            addr = self.initializers.pop(0)
+            self.call(addr, (self.argc[31:0], self.argv, self.envp), 'inside_init',
+                  prototype='int main(int argc, char **argv, char **envp)')
 
     def after_init(self, main, argc, argv, init, fini, exit_addr=0):
         self.call(self.main, (self.argc[31:0], self.argv, self.envp), 'after_main',


Test patch: