This project provides files that can be included in any C++ project running on Linux that provide the ability for the program to identify and utilize data and function objects from just a string, without having to manually register each attribute.
From Wikipedia,
Reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.
This feature is available in most modern languages, including Python and Javascript. Reflection allows the developer/user to gain information about how the program is structured, ie what functions are available, where they're located, what arguments they take, etc. However, it's distincly lacking from C++. Since C++ programs are compiled and linked before execution, the function names in your code ger replaced with the virtual address of where the machine can find them. While this greatly improves the performace of C++, there are some scenarios where it may be beneficial to know about how the program is structured. Consider an IoT device operating in a remote location that needs maintenance; rather than spending time writing diagnostic tools, a service member could remotely access the device and execute any of the code already used to control the device to identify and resolve the issue.
Using this project is as easy as including ElfReader.cpp
, ElfReader.h
, Reflection.cpp
, and Reflection.h
in your project. At some point in your code, instantiate the Reflection
Class as shown below, and it will automatically process the available symbols and make them available to the application.
Reflection *r = Reflection::FromCurrentProcess((void *)main);
There are two classes that power this project; Reflection
and ElfReader
. ElfReader
reads the ELF file describing the running process, and provides the symbols to Reflection
. Reflection
accepts arguments from the developer/user, and makes meaning from the symbols provided by ElfReader
.
Currently, the ElfReader gets the current process's ELF file by opening /proc/<pid>/exe
. This is really just a symlink to the executable elsewhere in the filesystem, but using the pid
is an easy way to get an absolute path, which is why we use it.
Once the ELF file is opened, ElfReader
parses the Section Header Table to identify the SHT_DYNSYM
entry, and makes a list of the SHT_STRTAB
and SHT_SYMTAB
entries.
ElfReader
also provides a function to lookup a symbol by a string, std::vector<Elf64_Sym*>* ElfReader::lookupSymbol(char *)
. This method goes through every entry in SHT_DYNSYM
and resolves its name by looking it up in one of the SHT_STRTAB
s, and de-mangling it. If the name that we're looking for exists in the resolved name, the symbol is added to a list of matches, which is returned when all of the entries have been analyzed.
The Reflection
Class should be instantiated by the Reflection::FromCurrentProcess(void *main)
method, which automatically identifies the process's pid
and load the ElfReader
. Passing the location of int main(int,char**)
allow the class to map the value of a symbol to a location in memory; Reflection
will lookup the main
symbol and calculate the difference between its value and the address passed to it. This offset is then added to any other symbol's value to determine it's location in memory.
The main method to note in Reflection
is int Reflection::exec(char*,void*)
. This currently allows the user to specify a function that they want to execute via the string method, and supply up to 6 arguments that are either strings or numbers. Due to the early stage of the project, only commands with one .
seperator work. The fvalue of the command before .
is used to identify an instance of a class, and the value after is used to determine the function to run. If no .
is needed, it will try to find a global function.
Passing the arguments to a function is done through inline assembly code. The value is pulled from the string passed to the method and stored in the appropriate register. When all registers have populated, inline assembly code is used to call the function.
Calling functions on an object instantiated from a class is also possible. The identified object is simply passed as the first argument to the function in compliance with the machine's ABI.
See if there's a way to also make this work for microcontrollers, where the code is flash directly and the ELF file is not available at runtime. May be limited to applications that have peripheral storage or implement an OS that loads the file.
Read debug symbols as well to provide more data and options to the user/developer. This could include access to local variables as well as source code and type matching.
Use the ElfReader
to read a different process's ELF file, and use a system call like strace
to take control of it.
Use the ElfReader
to read another ELF file. Load that program's code and link it to the existing process, therefore providing methods and functionality from the other object.