[COMMIT osv master] core: support launching statically linked executables

2 views
Skip to first unread message

Commit Bot

unread,
Oct 3, 2023, 2:26:28 PM10/3/23
to osv...@googlegroups.com, Waldemar Kozaczuk
From: Waldemar Kozaczuk <jwkoz...@gmail.com>
Committer: WALDEMAR KOZACZUK <jwkoz...@gmail.com>
Branch: master

core: support launching statically linked executables

This patch enhances the dynamic linker to support launching statically
linked executables.

The dynamically linked executables or shared libraries are typically launched
by calling a "main" function which is exported and can be resolved using
the ELF symbol table. The statically linked executables do not export
such symbol and instead must be launched by jumping to the ELF entry
point specified in the header. To that end this patch implements new
functions - run_entry_point() - specific for each architecture - x86_64 and
aarch64 which fundamentally do similar thing - put argc, argv,
environment variables and auxiliary vector on stack and jump to the elf
entry point.

In addition this patch enhances other parts of the dynamic linker logic
to do things a little bit differently for statically linked executables:

- do not relocate as the executable comes with its own code that does
this

- do not call INIT and FINI function for the same reason as above

- do not try to load dependant libraries as they would be none

This patch also adds augment_auxv() to add extra auxiliary vector
entries needed by statically linked executables to bootstrap
themselves

In addition, this 2nd version of the PR renames the method
is_statically_linked() to is_statically_linked_executable() and fixes
it by identifying if an ELF is not a shared library. So it should
fix the "liblua.so" problem reported by Nadav.

Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>

---
diff --git a/arch/aarch64/arch-elf.hh b/arch/aarch64/arch-elf.hh
--- a/arch/aarch64/arch-elf.hh
+++ b/arch/aarch64/arch-elf.hh
@@ -23,4 +23,38 @@ enum {

#define ELF_KERNEL_MACHINE_TYPE 183

+static constexpr unsigned SAFETY_BUFFER = 256;
+#include <osv/align.hh>
+
+inline void run_entry_point(void* ep, int argc, char** argv, int argv_size)
+{
+ //The layout of the stack and state of all relevant registers is similar
+ //to how it looks for x86_64. The main difference (possibly for now)
+ //is the inlined assembly
+ int argc_plus_argv_stack_size = argv_size + 1;
+
+ //Capture current stack pointer
+ void *stack;
+ asm volatile ("mov %0, sp" : "=r"(stack));
+
+ //The code below puts argv and auxv vector onto the stack but it may
+ //also end up using some of the stack. To make sure there is no collision
+ //let us leave some space - SAFETY_BUFFER - between current stack pointer
+ //and the position on the stack we will be writing to.
+ stack -= (SAFETY_BUFFER + argc_plus_argv_stack_size * sizeof(char*));
+
+ //According to the document above the stack pointer should be 16-bytes aligned
+ stack = align_down(stack, 16);
+
+ *reinterpret_cast<u64*>(stack) = argc;
+ memcpy(stack + sizeof(char*), argv, argv_size * sizeof(char*));
+
+ //Set stack pointer and jump to the ELF entry point
+ asm volatile (
+ "mov sp, %1\n\t" //set stack
+ "blr %0\n\t"
+ :
+ : "r"(ep), "r"(stack));
+}
+
#endif /* ARCH_ELF_HH */
diff --git a/arch/x64/arch-elf.hh b/arch/x64/arch-elf.hh
--- a/arch/x64/arch-elf.hh
+++ b/arch/x64/arch-elf.hh
@@ -42,4 +42,45 @@ enum {

#define ELF_KERNEL_MACHINE_TYPE 62

+static constexpr unsigned SAFETY_BUFFER = 256;
+#include <osv/align.hh>
+
+inline void run_entry_point(void* ep, int argc, char** argv, int argv_size)
+{
+ //The layout of the stack and state of all relevant registers is described
+ //in detail in the section 3.4 (Process Initialization) of the System V Application
+ //Binary Interface AMD64 Architecture Processor Supplement Draft Version 0.95
+ //(see https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf)
+ int argc_plus_argv_stack_size = argv_size + 1;
+
+ //Capture current stack pointer
+ void *stack;
+ asm volatile ("movq %%rsp, %0" : "=r"(stack));
+
+ //The code below puts argv and auxv vector onto the stack but it may
+ //also end up using some of the stack. To make sure there is no collision
+ //let us leave some space - SAFETY_BUFFER - between current stack pointer
+ //and the position on the stack we will be writing to.
+ stack -= (SAFETY_BUFFER + argc_plus_argv_stack_size * sizeof(char*));
+
+ //According to the document above the stack pointer should be 16-bytes aligned
+ stack = align_down(stack, 16);
+
+ //... and it should start with argc, followed by argv, environment pointers and
+ //auxiliary vector entries. For details look at application::prepare_argv()
+ *reinterpret_cast<u64*>(stack) = argc;
+ memcpy(stack + sizeof(char*), argv, argv_size * sizeof(char*));
+
+ //TODO: Reset SSE2 and floating point registers and RFLAGS as the "Special Registers"
+ // paragraph of the section of 3.4 (Process Initialization) of the "System V Application
+ // Binary Interface" document states
+ //Set stack pointer, reset rdx and jump to the ELF entry point
+ asm volatile (
+ "movq %1, %%rsp\n\t" //set stack
+ "movq $0, %%rdx\n\t" //fini should be 0 for now (TODO: Eventually point to atexit())
+ "jmpq *%0\n\t"
+ :
+ : "r"(ep), "r"(stack));
+}
+
#endif /* ARCH_ELF_HH */
diff --git a/core/app.cc b/core/app.cc
--- a/core/app.cc
+++ b/core/app.cc
@@ -217,12 +217,19 @@ application::application(const std::string& command,
throw launch_error("Failed to load object: " + command);
}

- _main = _lib->lookup<int (int, char**)>(main_function_name.c_str());
- if (!_main) {
- _entry_point = reinterpret_cast<void(*)()>(_lib->entry_point());
- }
- if (!_entry_point && !_main) {
- throw launch_error("Failed looking up main");
+ if (_lib->is_statically_linked_executable()) {
+ //Augment auxiliary vector with extra entries like AT_PHDR, AT_ENTRY, etc
+ //that are necessary by a static executable to bootstrap itself
+ augment_auxv();
+ } else {
+ _main = _lib->lookup<int (int, char**)>(main_function_name.c_str());
+
+ if (!_main) {
+ _entry_point = reinterpret_cast<void(*)()>(_lib->entry_point());
+ }
+ if (!_entry_point && !_main) {
+ throw launch_error("Failed looking up main");
+ }
}
}

@@ -319,22 +326,27 @@ void application::main()
elf::get_program()->init_library(_args.size(), _argv.get());
sched::thread::current()->set_name(_command);

- if (_main) {
- run_main();
+ if (_lib->is_statically_linked_executable()) {
+ run_entry_point(_lib->entry_point(), _args.size(), _argv.get(), _argv_size);
} else {
- // The application is expected not to initialize the environment in
- // which it runs on its owns but to call __libc_start_main(). If that's
- // not the case bad things may happen: constructors of global objects
- // may be called twice, TLS may be overriden and the program may not
- // received correct arguments, environment variables and auxiliary
- // vector.
- _entry_point();
+ if (_main) {
+ run_main();
+ } else {
+ // The application is expected not to initialize the environment in
+ // which it runs on its owns but to call __libc_start_main(). If that's
+ // not the case bad things may happen: constructors of global objects
+ // may be called twice, TLS may be overriden and the program may not
+ // received correct arguments, environment variables and auxiliary
+ // vector.
+ _entry_point();
+ }
}
// _entry_point() doesn't return
}

static u64 random_bytes[2];

+static constexpr int max_auxv_parameters_count = 8;
void application::prepare_argv(elf::program *program)
{
// Prepare program_* variable used by the libc
@@ -372,9 +384,9 @@ void application::prepare_argv(elf::program *program)
reinterpret_cast<int*>(random_bytes)[idx] = rand_r(&seed);
}

- int auxv_parameters_count = 4;
// Allocate the continuous buffer for argv[] and envp[]
- _argv.reset(new char*[_args.size() + 1 + envcount + 1 + sizeof(Elf64_auxv_t) * (auxv_parameters_count + 1)]);
+ _argv_size = _args.size() + 1 + envcount + 1 + sizeof(Elf64_auxv_t) * (max_auxv_parameters_count + 1);
+ _argv.reset(new char*[_argv_size]);

// Fill the argv part of these buffers
char *ab = _argv_buf.get();
@@ -394,25 +406,49 @@ void application::prepare_argv(elf::program *program)
}
contig_argv[_args.size() + 1 + envcount] = nullptr;

- Elf64_auxv_t* _auxv =
- reinterpret_cast<Elf64_auxv_t *>(&contig_argv[_args.size() + 1 + envcount + 1]);
- int auxv_idx = 0;
+ _auxv = reinterpret_cast<Elf64_auxv_t *>(&contig_argv[_args.size() + 1 + envcount + 1]);
+ _auxv_idx = 0;

// Pass the VDSO library to the application.
- _auxv[auxv_idx].a_type = AT_SYSINFO_EHDR;
- _auxv[auxv_idx++].a_un.a_val = reinterpret_cast<uint64_t>(program->get_libvdso_base());
+ _auxv[_auxv_idx].a_type = AT_SYSINFO_EHDR;
+ _auxv[_auxv_idx++].a_un.a_val = reinterpret_cast<uint64_t>(program->get_libvdso_base());
+
+ _auxv[_auxv_idx].a_type = AT_PAGESZ;
+ _auxv[_auxv_idx++].a_un.a_val = sysconf(_SC_PAGESIZE);
+
+ _auxv[_auxv_idx].a_type = AT_MINSIGSTKSZ;
+ _auxv[_auxv_idx++].a_un.a_val = sysconf(_SC_MINSIGSTKSZ);
+
+ _auxv[_auxv_idx].a_type = AT_RANDOM;
+ _auxv[_auxv_idx++].a_un.a_val = reinterpret_cast<uint64_t>(random_bytes);
+
+ _auxv[_auxv_idx].a_type = AT_NULL;
+ _auxv[_auxv_idx].a_un.a_val = 0;
+}
+
+// Augments auxiliary vector with extra entries like AT_PHDR, AT_ENTRY, etc
+// that are necessary by a static executable to bootstrap itself.
+// Please note these entries are _lib/ELF specific unlike the entries
+// set by prepare_argv()
+void application::augment_auxv()
+{
+ //Let us verify there is space for 4 extra entries needed
+ assert(_auxv_idx + 4 == max_auxv_parameters_count);
+
+ _auxv[_auxv_idx].a_type = AT_PHDR;
+ _auxv[_auxv_idx++].a_un.a_val = reinterpret_cast<uint64_t>(_lib->headers_start());

- _auxv[auxv_idx].a_type = AT_PAGESZ;
- _auxv[auxv_idx++].a_un.a_val = sysconf(_SC_PAGESIZE);
+ _auxv[_auxv_idx].a_type = AT_PHENT;
+ _auxv[_auxv_idx++].a_un.a_val = _lib->headers_size();

- _auxv[auxv_idx].a_type = AT_MINSIGSTKSZ;
- _auxv[auxv_idx++].a_un.a_val = sysconf(_SC_MINSIGSTKSZ);
+ _auxv[_auxv_idx].a_type = AT_PHNUM;
+ _auxv[_auxv_idx++].a_un.a_val = _lib->headers_count();

- _auxv[auxv_idx].a_type = AT_RANDOM;
- _auxv[auxv_idx++].a_un.a_val = reinterpret_cast<uint64_t>(random_bytes);
+ _auxv[_auxv_idx].a_type = AT_ENTRY;
+ _auxv[_auxv_idx++].a_un.a_val = reinterpret_cast<uint64_t>(_lib->entry_point());

- _auxv[auxv_idx].a_type = AT_NULL;
- _auxv[auxv_idx].a_un.a_val = 0;
+ _auxv[_auxv_idx].a_type = AT_NULL;
+ _auxv[_auxv_idx].a_un.a_val = 0;
}

void application::run_main()
diff --git a/core/elf.cc b/core/elf.cc
--- a/core/elf.cc
+++ b/core/elf.cc
@@ -372,9 +372,11 @@ void object::set_base(void* base)
// Override the passed in value as the base for non-PICs (Position Dependant Executables)
// needs to be set to 0 because all the addresses in it are absolute
_base = 0x0;
+ _headers_start = reinterpret_cast<void*>(p->p_vaddr) + _ehdr.e_phoff;
} else {
// Otherwise for kernel, PIEs and shared libraries set the base as requested by caller
_base = align(base, p->p_align, p->p_vaddr & (p->p_align - 1)) - p->p_vaddr;
+ _headers_start = _base + _ehdr.e_phoff;
}

_end = _base + q->p_vaddr + q->p_memsz;
@@ -533,7 +535,7 @@ void object::process_headers()
abort("Unknown p_type in executable %s: %d\n", pathname(), phdr.p_type);
}
}
- if (!is_core() && is_statically_linked()) {
+ if (!is_core() && is_statically_linked_executable()) {
abort("Statically linked executables are not supported yet!\n");
}
if (_is_dynamically_linked_executable && _tls_segment) {
@@ -597,11 +599,6 @@ void object::fix_permissions()
make_text_writable(false);
}

- //Full RELRO applies to dynamically linked executables only
- if (is_statically_linked()) {
- return;
- }
-
//Process GNU_RELRO segments only to make GOT and others read-only
for (auto&& phdr : _phdrs) {
if (phdr.p_type != PT_GNU_RELRO)
@@ -891,9 +888,6 @@ constexpr Elf64_Versym old_version_symbol_mask = Elf64_Versym(1) << 15;

Elf64_Sym* object::lookup_symbol_old(const char* name)
{
- if (!dynamic_exists(DT_SYMTAB)) {
- return nullptr;
- }
auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
auto strtab = dynamic_ptr<char>(DT_STRTAB);
auto hashtab = dynamic_ptr<Elf64_Word>(DT_HASH);
@@ -923,9 +917,6 @@ dl_new_hash(const char *s)

Elf64_Sym* object::lookup_symbol_gnu(const char* name, bool self_lookup)
{
- if (!dynamic_exists(DT_SYMTAB)) {
- return nullptr;
- }
auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
auto strtab = dynamic_ptr<char>(DT_STRTAB);
auto hashtab = dynamic_ptr<Elf64_Word>(DT_GNU_HASH);
@@ -1080,9 +1071,6 @@ static std::string dirname(std::string path)

void object::load_needed(std::vector<std::shared_ptr<object>>& loaded_objects)
{
- if (!dynamic_exists(DT_NEEDED)) {
- return;
- }
std::vector<std::string> rpath;

std::string rpath_str;
@@ -1181,6 +1169,9 @@ std::string object::pathname()
// Run the object's static constructors or similar initialization
void object::run_init_funcs(int argc, char** argv)
{
+ if (is_statically_linked_executable()) {
+ return;
+ }
// Invoke any init functions if present and pass in argc and argv
// The reason why we pass argv and argc is explained in issue #795
if (dynamic_exists(DT_INIT)) {
@@ -1206,6 +1197,9 @@ void object::run_init_funcs(int argc, char** argv)
// Run the object's static destructors or similar finalization
void object::run_fini_funcs()
{
+ if (is_statically_linked_executable()) {
+ return;
+ }
if(!_init_called){
return;
}
@@ -1478,12 +1472,17 @@ program::load_object(std::string name, std::vector<std::string> extra_path,
ef->load_segments();
ef->process_headers();
if (ef->is_pic())
- _next_alloc = ef->end();
+ _next_alloc = ef->end();
add_debugger_obj(ef.get());
loaded_objects.push_back(ef);
- ef->load_needed(loaded_objects);
- ef->relocate();
- ef->fix_permissions();
+ //Do not relocate static executables as they are linked with its own
+ //dynamic linker. Also do not try to load any dependant libraries
+ //as they do not apply to statically linked executables.
+ if (!ef->is_statically_linked_executable()) {
+ ef->load_needed(loaded_objects);
+ ef->relocate();
+ ef->fix_permissions();
+ }
_files[name] = ef;
_files[ef->soname()] = ef;
return ef;
diff --git a/include/osv/app.hh b/include/osv/app.hh
--- a/include/osv/app.hh
+++ b/include/osv/app.hh
@@ -217,6 +217,7 @@ private:
void start_and_join(waiter* setup_waiter);
void main();
void prepare_argv(elf::program *program);
+ void augment_auxv();
void run_main();
friend void ::__libc_start_main(int(*)(int, char**), int, char**, void(*)(),
void(*)(), void(*)(), void*);
@@ -241,8 +242,11 @@ private:
// retained as member variable so that it later can be passed as argument by either
// application::main and application::run_main() or application::run_main() called
// from __libc_start_main()
+ int _argv_size;
std::unique_ptr<char *[]> _argv;
std::unique_ptr<char []> _argv_buf; // actual arguments content _argv points to
+ Elf64_auxv_t* _auxv;
+ int _auxv_idx;

// Must be destroyed before _lib, because contained function objects may
// have destructors which are part of the application's code.
diff --git a/include/osv/elf.hh b/include/osv/elf.hh
--- a/include/osv/elf.hh
+++ b/include/osv/elf.hh
@@ -212,6 +212,7 @@ enum {
};
enum {
DF_1_NOW = 0x1,
+ DF_1_PIE = 0x08000000,
};

enum {
@@ -378,13 +379,39 @@ public:
size_t initial_tls_size() { return _initial_tls_size; }
void* initial_tls() { return _initial_tls.get(); }
void* get_tls_segment() { return _tls_segment; }
- bool is_pic() { return _ehdr.e_type != ET_EXEC; }
std::vector<ptrdiff_t>& initial_tls_offsets() { return _initial_tls_offsets; }
+ // OSv is only "interested" in ELF objects with e_type equal to ET_EXEC or ET_DYN
+ // and rejects others (see load_elf_header()). All these can be broken down
+ // into five types:
+ // - (1) dynamically linked position dependent executable
+ // - (2) dynamically linked position independent executable (dynamically linked PIE)
+ // - (3) statically linked position dependent executable
+ // - (4) statically linked position independent executable (statically linked PIE)
+ // - (5) shared library
+ // As OSv processes the ELF objects, most of the time it needs to know if given
+ // object belongs to a superset of these types - dynamically linked executables,
+ // statically linked executables, position dependent object, etc. For this reason
+ // the methods below provide a way to make such determination.
+ //
+ // Is it a position independent code (type 2, 4 or 5)?
+ bool is_pic() { return _ehdr.e_type == ET_DYN; }
+ // Is it a position independent executable (type 2 or 4)?
+ bool is_pie() { return dynamic_exists(DT_FLAGS_1) && (dynamic_val(DT_FLAGS_1) & DF_1_PIE); }
+ // Is it a shared library (type 5)?
+ bool is_shared_library() { return _ehdr.e_type == ET_DYN && !is_pie(); }
+ // Is it a dynamically linked executable (type 1 or 2, determined by presence of PT_INTERP)?
bool is_dynamically_linked_executable() { return _is_dynamically_linked_executable; }
+ // Is it a statically linked executable (type 3 or 4)?
+ // Absence of PT_INTERP is not enough to determine it is a statically linked executable
+ // as shared libraries also as missing PT_INTERP.
+ bool is_statically_linked_executable() { return !_is_dynamically_linked_executable && !is_shared_library(); }
ulong get_tls_size();
ulong get_aligned_tls_size();
void copy_local_tls(void* to_addr);
void* eh_frame_addr() { return _eh_frame; }
+ Elf64_Half headers_count() { return _ehdr.e_phnum; }
+ Elf64_Half headers_size() { return _ehdr.e_phentsize; }
+ void* headers_start() { return _headers_start; }
protected:
virtual void load_segment(const Elf64_Phdr& segment) = 0;
virtual void unload_segment(const Elf64_Phdr& segment) = 0;
@@ -415,7 +442,6 @@ private:
void prepare_local_tls(std::vector<ptrdiff_t>& offsets);
void alloc_static_tls();
void make_text_writable(bool flag);
- bool is_statically_linked() { return !_is_dynamically_linked_executable && _ehdr.e_entry; }
protected:
program& _prog;
std::string _pathname;
@@ -440,6 +466,7 @@ protected:
bool is_core();
bool _init_called;
void* _eh_frame;
+ void* _headers_start;

std::unordered_map<std::string,void*> _cached_symbols;

Reply all
Reply to author
Forward
0 new messages