diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -28,6 +28,10 @@ add_subdirectory(include) add_subdirectory(utils) +# The loader can potentially depend on the library components so add it +# after the library implementation directories. +add_subdirectory(loader) + # The lib and test directories are added at the very end as tests # and libraries potentially draw from the components present in all # of the other directories. diff --git a/libc/loader/CMakeLists.txt b/libc/loader/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/loader/CMakeLists.txt @@ -0,0 +1,3 @@ +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${LIBC_TARGET_OS}) +endif() diff --git a/libc/loader/linux/CMakeLists.txt b/libc/loader/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/loader/linux/CMakeLists.txt @@ -0,0 +1,34 @@ +function(add_loader_object name) + cmake_parse_arguments( + "ADD_LOADER_OBJECT" + "" # No option arguments + "SRC" # Single value arguments + "DEPENDS;COMPILE_OPTIONS" # Multi value arguments + ${ARGN} + ) + add_object( + ${name}_object + SRC ${ADD_LOADER_OBJECT_SRC} + DEPENDS ${ADD_LOADER_OBJECT_DEPENDS} + COMPILE_OPTIONS ${ADD_LOADER_OBJECT_COMPILE_OPTIONS} + ) + + set(objfile ${LIBC_BUILD_DIR}/lib/${name}.o) + add_custom_command( + OUTPUT ${objfile} + COMMAND cp $ ${objfile} + DEPENDS $ + ) + add_custom_target( + ${name} + DEPENDS ${objfile} + ) + set_target_properties( + ${name} + PROPERTIES + "TARGET_TYPE" "LOADER_OBJECT" + "OBJECT_FILE" ${objfile} + ) +endfunction() + +add_subdirectory(${LIBC_TARGET_MACHINE}) diff --git a/libc/loader/linux/x86_64/CMakeLists.txt b/libc/loader/linux/x86_64/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/loader/linux/x86_64/CMakeLists.txt @@ -0,0 +1,11 @@ +add_loader_object( + crt1 + SRC + start.cpp + DEPENDS + linux_syscall_h + sys_syscall_h + COMPILE_OPTIONS + -fno-omit-frame-pointer + -w # For one, we call main which is not allowed by the C++ standard. +) diff --git a/libc/loader/linux/x86_64/start.cpp b/libc/loader/linux/x86_64/start.cpp new file mode 100644 --- /dev/null +++ b/libc/loader/linux/x86_64/start.cpp @@ -0,0 +1,62 @@ +//===------------------ Implementation of crt for x86_64 ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "config/linux/syscall.h" +#include "include/sys/syscall.h" + +#include +#include + +extern "C" int main(int, char **, char **); + +struct Args { + // At the language level, argc is an int. But we use uint64_t as the x86_64 + // ABI specifies it as an 8 byte value. + uint64_t argc; + + // At the language level, argv is a char** value. However, we use uint64_t as + // the x86_64 ABI specifies the argv vector be an |argc| long array of 8-byte + // values. + uint64_t argv[]; +}; + +// TODO: Would be nice to use the aux entry structure from elf.h when available. +struct AuxEntry { + uint64_t type; + uint64_t value; +}; + +extern "C" void _start() { + uintptr_t *frame_ptr = + reinterpret_cast(__builtin_frame_address(0)); + + // This TU is compiled with -fno-omit-frame-pointer. Hence, the previous value + // of the base pointer pushed on to the stack. So, we step over it (the "+ 1" + // below) to get to the args. + Args *args = reinterpret_cast(frame_ptr + 1); + + // After the argv array, is a 8-byte long NULL value before the array of env + // values. The end of the env values is marked by another 8-byte long NULL + // value. We step over it (the "+ 1" below) to get to the env values; + uint64_t *env_ptr = args->argv + args->argc + 1; + uint64_t *env_end_marker = env_ptr; + while (*env_end_marker != 0) + ++env_end_marker; + + // After the env array, is the aux-vector. The end of the aux-vector is + // denoted by an AT_NULL entry. + for (AuxEntry *aux_entry = reinterpret_cast(env_end_marker + 1); + aux_entry->type != AT_NULL; ++aux_entry) { + // TODO: Read the aux vector and store necessary information in a libc wide + // data structure. + } + + __llvm_libc::syscall(SYS_exit, + main(args->argc, reinterpret_cast(args->argv), + reinterpret_cast(env_ptr))); +} diff --git a/libc/test/CMakeLists.txt b/libc/test/CMakeLists.txt --- a/libc/test/CMakeLists.txt +++ b/libc/test/CMakeLists.txt @@ -1,4 +1,5 @@ add_custom_target(check-libc) add_subdirectory(config) +add_subdirectory(loader) add_subdirectory(src) diff --git a/libc/test/loader/CMakeLists.txt b/libc/test/loader/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/loader/CMakeLists.txt @@ -0,0 +1,71 @@ +add_libc_testsuite(libc_loader_tests) + +# A rule to add loader tests. When we have a complete loader, we should +# be able to use the add_libc_unittest rule or an extension of it. But, +# while the loader is getting built, we need to use a special rule like +# this. +function(add_loader_test target_name) + cmake_parse_arguments( + "ADD_LOADER_TEST" + "" # No option arguments + "SRC" # Single value arguments + "DEPENDS;ARGS;ENV" # Multivalue arguments. + ${ARGN} + ) + + set(main_target ${target_name}_main_object) + add_library( + ${main_target} + OBJECT + ${ADD_LOADER_TEST_SRC} + ) + + target_include_directories( + ${main_target} + PRIVATE + ${LIBC_SOURCE_DIR} + ${LIBC_BUILD_DIR} + ${LIBC_BUILD_DIR}/include + ) + + set(dep_objects "") + if(ADD_LOADER_TEST_DEPENDS) + add_dependencies(${main_target} ${ADD_LOADER_TEST_DEPENDS}) + foreach(dep IN LISTS ADD_LOADER_TEST_DEPENDS) + get_target_property(objfile ${dep} "OBJECT_FILE") + if(NOT objfile) + message( + FATAL_ERROR + "Unexpected dependency of an `add_loader_test` target. A dependency " + "should be a target of type `add_entrypoint_object, `add_object`, or " + "`add_loader_object`.") + endif() + list(APPEND dep_objects ${objfile}) + endforeach(dep) + endif() + + set(test_exe ${CMAKE_CURRENT_BINARY_DIR}/${target_name}.exe) + + add_custom_command( + OUTPUT ${test_exe} + DEPENDS $ ${ADD_LOADER_TEST_DEPENDS} + COMMAND ${CMAKE_LINKER} -static ${loader_object} $ ${dep_objects} -o ${test_exe} + ) + + add_custom_target( + ${target_name} + DEPENDS ${test_exe} + ) + + add_custom_command( + TARGET ${target_name} + POST_BUILD + COMMAND ${ADD_LOADER_TEST_ENV} ${test_exe} ${ADD_LOADER_TEST_ARGS} + ) + + add_dependencies(libc_loader_tests ${target_name}) +endfunction(add_loader_test) + +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) + add_subdirectory(${LIBC_TARGET_OS}) +endif() diff --git a/libc/test/loader/linux/CMakeLists.txt b/libc/test/loader/linux/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/loader/linux/CMakeLists.txt @@ -0,0 +1,16 @@ +add_loader_test( + loader_args_test + SRC + args_test.cpp + DEPENDS + __assert_fail + _Exit + abort + crt1 + raise + ARGS + 1 2 3 + ENV + FRANCE=Paris + GERMANY=Berlin +) diff --git a/libc/test/loader/linux/args_test.cpp b/libc/test/loader/linux/args_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/loader/linux/args_test.cpp @@ -0,0 +1,39 @@ +//===----------------- Loader test to check args to main ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#undef NDEBUG +#include "src/assert/assert.h" + +static bool my_strcmp(const char *lhs, const char *rhs) { + const char *l, *r; + for (l = lhs, r = rhs; *l != '\0' && *r != '\0'; ++l, ++r) + if (*l != *r) + return false; + + return *l == '\0' && *r == '\0'; +} + +int main(int argc, char **argv, char **envp) { + assert(argc == 4 && "Unexpected argc."); + assert(my_strcmp(argv[1], "1") && "Unexpected argv[1]."); + assert(my_strcmp(argv[2], "2") && "Unexpected argv[1]."); + assert(my_strcmp(argv[3], "3") && "Unexpected argv[1]."); + + bool found_france = false; + bool found_germany = false; + for (; *envp != nullptr; ++envp) { + if (my_strcmp(*envp, "FRANCE=Paris")) + found_france = true; + if (my_strcmp(*envp, "GERMANY=Berlin")) + found_germany = true; + } + assert(found_france && found_germany && + "Did not find whats expected in envp."); + + return 0; +}