|
/** |
|
* @brief A C++ program to read its own compiler flags. |
|
* |
|
* This program demonstrates how to read compiler options stored in an executable |
|
* by the compiler-specific flags. It requires an ELF-based system like Linux. |
|
* It parses the executable's ELF headers to find the specific section containing the flags. |
|
* |
|
* How it works: |
|
* 1. It locates its own executable file path via the /proc/self/exe symlink. |
|
* 2. It opens the executable file in binary mode. |
|
* 3. It reads the main ELF header to get information about the file structure. |
|
* 4. It locates and reads the Section Header Table. |
|
* 5. It finds the Section Header String Table, which contains the names of all sections. |
|
* 6. It iterates through all sections, looking for one named ".GCC.command.line". |
|
* 7. Once found, it reads the content of that section and prints it. The content |
|
* is a series of null-terminated strings representing the compiler arguments. |
|
* |
|
* References: |
|
* |
|
* https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-frecord-gcc-switches |
|
* https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-frecord-command-line |
|
* |
|
* Compilation Instructions: |
|
* You MUST compile this program with the -frecord-* flag appropriate to the compiler |
|
* |
|
* Examples: |
|
* g++ -frecord-gcc-switches -O2 -Wall -o read_flags compiler_options.cpp |
|
* |
|
* clang++ -frecord-command-line -O2 -Wall -o read_flags compiler_options.cpp |
|
* |
|
* Example program output: |
|
* |
|
* GNU C++17 13.3.0 -mtune=generic -march=x86-64 -O2 -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection |
|
* |
|
* /usr/lib/llvm-18/bin/clang --driver-mode=g++ -frecord-command-line -O2 -Wall -o read_flags compiler_options.cpp -dumpdir read_flags- |
|
* |
|
* Running the program: |
|
* ./read_flags |
|
*/ |
|
|
|
#include <iostream> |
|
#include <fstream> |
|
#include <string_view> |
|
#include <vector> |
|
|
|
#if __has_include(<elf.h>) |
|
#define HAS_ELF |
|
#include <elf.h> // Required for ELF header structures. |
|
#elif defined(__APPLE__) |
|
#include <mach-o/dyld.h> // Required for Mach-O header structures. |
|
#include <mach-o/loader.h> |
|
#include <climits> // for PATH_MAX |
|
#elif defined(_WIN32) |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
#include <psapi.h> |
|
#else |
|
#error "This is not a supported platform. Try a Linux virtual machine or macOS." |
|
#endif |
|
|
|
// A utility function to print the flags from the data buffer. |
|
// The flags are stored as a sequence of null-terminated strings. |
|
void print_flags(const std::vector<char>& buffer) { |
|
const char* current = buffer.data(); |
|
const char* end = buffer.data() + buffer.size(); |
|
|
|
while (current < end) { |
|
std::string_view flag(current); |
|
if (!flag.empty()) { |
|
std::cout << " " << flag << "\n"; |
|
} |
|
// Move to the next string |
|
current += flag.length() + 1; |
|
} |
|
} |
|
|
|
|
|
bool parse_elf(){ |
|
bool found = false; |
|
#ifdef HAS_ELF |
|
// On Linux, /proc/self/exe is a symbolic link to the current process's executable. |
|
const char* exe_path = "/proc/self/exe"; |
|
|
|
std::ifstream file(exe_path, std::ios::binary); |
|
if (!file) { |
|
std::cerr << "Error: Could not open own executable at " << exe_path << "\n"; |
|
return false; |
|
} |
|
|
|
// 1. Read the main ELF header (we assume 64-bit ELF format) |
|
Elf64_Ehdr elf_header; |
|
file.read(reinterpret_cast<char*>(&elf_header), sizeof(elf_header)); |
|
|
|
// Basic validation: Check for the ELF magic number |
|
if (file.gcount() != sizeof(elf_header) || |
|
elf_header.e_ident[EI_MAG0] != ELFMAG0 || |
|
elf_header.e_ident[EI_MAG1] != ELFMAG1 || |
|
elf_header.e_ident[EI_MAG2] != ELFMAG2 || |
|
elf_header.e_ident[EI_MAG3] != ELFMAG3) { |
|
std::cerr << "Error: The file is not a valid ELF executable.\n"; |
|
return false; |
|
} |
|
|
|
// 2. Read the Section Header Table |
|
file.seekg(elf_header.e_shoff); |
|
std::vector<Elf64_Shdr> section_headers(elf_header.e_shnum); |
|
file.read(reinterpret_cast<char*>(section_headers.data()), elf_header.e_shnum * sizeof(Elf64_Shdr)); |
|
if (!file) { |
|
std::cerr << "Error: Could not read section headers.\n"; |
|
return false; |
|
} |
|
|
|
// 3. Read the Section Header String Table |
|
// This table contains the names of all the sections. |
|
const Elf64_Shdr& shstrtab_header = section_headers[elf_header.e_shstrndx]; |
|
std::vector<char> shstrtab(shstrtab_header.sh_size); |
|
file.seekg(shstrtab_header.sh_offset); |
|
file.read(shstrtab.data(), shstrtab_header.sh_size); |
|
if (!file) { |
|
std::cerr << "Error: Could not read section header string table.\n"; |
|
return false; |
|
} |
|
|
|
// 4. Find the ".GCC.command.line" section and print its contents |
|
for (const auto& section_header : section_headers) { |
|
// sh_name is an offset into the section header string table |
|
std::string_view section_name = &shstrtab[section_header.sh_name]; |
|
|
|
if (section_name == ".GCC.command.line") { |
|
found = true; |
|
std::cout << "Found compiler flags in section '.GCC.command.line':\n"; |
|
|
|
// Read the section's content |
|
std::vector<char> flags_buffer(section_header.sh_size); |
|
file.seekg(section_header.sh_offset); |
|
file.read(flags_buffer.data(), section_header.sh_size); |
|
|
|
if (file) { |
|
print_flags(flags_buffer); |
|
} else { |
|
std::cerr << "Error: Failed to read the flags data.\n"; |
|
} |
|
break; // Stop after finding the section |
|
} |
|
} |
|
#endif |
|
return found; |
|
} |
|
|
|
|
|
bool parse_macho(){ |
|
bool found = false; |
|
#if defined(__APPLE__) |
|
char exe_path[PATH_MAX]; |
|
uint32_t size = sizeof(exe_path); |
|
if (_NSGetExecutablePath(exe_path, &size) != 0) { |
|
std::cerr << "Error: Could not get executable path.\n"; |
|
return false; |
|
} |
|
|
|
std::ifstream file(exe_path, std::ios::binary); |
|
if (!file) { |
|
std::cerr << "Error: Could not open own executable at " << exe_path << "\n"; |
|
return false; |
|
} |
|
|
|
mach_header_64 header; |
|
file.read(reinterpret_cast<char*>(&header), sizeof(header)); |
|
|
|
if (header.magic != MH_MAGIC_64) { |
|
std::cerr << "Error: Not a 64-bit Mach-O executable.\n"; |
|
return false; |
|
} |
|
|
|
// The load commands start right after the header. |
|
// We read them all into a buffer for easier parsing. |
|
std::vector<char> load_commands_buffer(header.sizeofcmds); |
|
file.read(load_commands_buffer.data(), header.sizeofcmds); |
|
if(!file) { |
|
std::cerr << "Error: Could not read load commands.\n"; |
|
return false; |
|
} |
|
|
|
const char* current_cmd_ptr = load_commands_buffer.data(); |
|
|
|
for (uint32_t i = 0; i < header.ncmds; ++i) { |
|
const load_command* lc = reinterpret_cast<const load_command*>(current_cmd_ptr); |
|
|
|
if (lc->cmd == LC_SEGMENT_64) { |
|
const segment_command_64* seg = reinterpret_cast<const segment_command_64*>(lc); |
|
|
|
if (std::string_view(seg->segname) == "__TEXT") { |
|
const section_64* sections = reinterpret_cast<const section_64*>( |
|
reinterpret_cast<const char*>(seg) + sizeof(segment_command_64) |
|
); |
|
|
|
for (uint32_t j = 0; j < seg->nsects; ++j) { |
|
const section_64& sect = sections[j]; |
|
std::string_view sect_name(sect.sectname); |
|
if (sect_name == "__clang_cmdline" || sect_name == "__command_line") { |
|
found = true; |
|
std::cout << "Found compiler flags in section '__TEXT," << sect_name << "':\n"; |
|
|
|
std::vector<char> flags_buffer(sect.size); |
|
file.seekg(sect.offset); |
|
file.read(flags_buffer.data(), sect.size); |
|
|
|
if (file) { |
|
print_flags(flags_buffer); |
|
} else { |
|
std::cerr << "Error: Failed to read the flags data.\n"; |
|
} |
|
return found; // Found it, we are done. |
|
} |
|
} |
|
} |
|
} |
|
|
|
current_cmd_ptr += lc->cmdsize; // Move to the next load command |
|
} |
|
#endif |
|
return found; |
|
} |
|
|
|
bool parse_pe() { |
|
bool found = false; |
|
#if defined(_WIN32) |
|
char exe_path[MAX_PATH]; |
|
if (GetModuleFileNameExA(GetCurrentProcess(), nullptr, exe_path, MAX_PATH) == 0) { |
|
std::cerr << "Error: Could not get executable path. Error code: " << GetLastError() << "\n"; |
|
return false; |
|
} |
|
|
|
std::ifstream file(exe_path, std::ios::binary); |
|
if (!file) { |
|
std::cerr << "Error: Could not open own executable at " << exe_path << "\n"; |
|
return false; |
|
} |
|
|
|
IMAGE_DOS_HEADER dos_header; |
|
file.read(reinterpret_cast<char*>(&dos_header), sizeof(dos_header)); |
|
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) { |
|
std::cerr << "Error: Not a valid PE file (Invalid DOS signature).\n"; |
|
return false; |
|
} |
|
|
|
// Seek to the PE header |
|
file.seekg(dos_header.e_lfanew); |
|
IMAGE_NT_HEADERS64 nt_headers; |
|
file.read(reinterpret_cast<char*>(&nt_headers), sizeof(nt_headers)); |
|
if (nt_headers.Signature != IMAGE_NT_SIGNATURE) { |
|
std::cerr << "Error: Not a valid PE file (Invalid NT signature).\n"; |
|
return false; |
|
} |
|
|
|
// The section headers are right after the NT headers |
|
IMAGE_SECTION_HEADER section_header; |
|
for (int i = 0; i < nt_headers.FileHeader.NumberOfSections; ++i) { |
|
file.read(reinterpret_cast<char*>(§ion_header), sizeof(section_header)); |
|
std::string_view section_name(reinterpret_cast<const char*>(section_header.Name), 8); |
|
// Trim null characters from the section name |
|
section_name = section_name.substr(0, section_name.find('\0')); |
|
|
|
if (section_name == ".cmdline") { |
|
found = true; |
|
std::cout << "Found compiler flags in section '.cmdline':\n"; |
|
|
|
std::vector<char> flags_buffer(section_header.SizeOfRawData); |
|
file.seekg(section_header.PointerToRawData); |
|
file.read(flags_buffer.data(), static_cast<std::streamsize>(flags_buffer.size())); |
|
|
|
if (file) { |
|
print_flags(flags_buffer); |
|
} else { |
|
std::cerr << "Error: Failed to read the flags data.\n"; |
|
} |
|
break; |
|
} |
|
} |
|
#endif |
|
return found; |
|
} |
|
|
|
int main() { |
|
|
|
bool found = false; |
|
#if defined HAS_ELF |
|
found = parse_elf(); |
|
#elif defined(__APPLE__) |
|
found = parse_macho(); |
|
#elif defined(_WIN32) |
|
found = parse_pe(); |
|
#endif |
|
if (!found) { |
|
std::cerr << "Could not find the compiler options section; this compiler/platform may not be supported.\n"; |
|
return 1; |
|
} |
|
|
|
return 0; |
|
} |