/*!A cross-platform build utility based on Lua * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright (C) 2015-present, Xmake Open Source Community. * * @author ruki * @file prefix.h * */ #ifndef XM_BINUTILS_AR_PREFIX_H #define XM_BINUTILS_AR_PREFIX_H /* ////////////////////////////////////////////////////////////////////////////////////// * includes */ #include "../prefix.h" #include "../coff/prefix.h" #include "../elf/prefix.h" #include "../macho/prefix.h" #include "../wasm/prefix.h" /* ////////////////////////////////////////////////////////////////////////////////////// * forward declarations */ extern tb_bool_t xm_binutils_coff_read_symbols(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua); extern tb_bool_t xm_binutils_elf_read_symbols(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua); extern tb_bool_t xm_binutils_macho_read_symbols(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua); extern tb_bool_t xm_binutils_wasm_read_symbols(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua); /* ////////////////////////////////////////////////////////////////////////////////////// * types */ #include "tbox/prefix/packed.h" typedef struct __xm_ar_header_t { tb_char_t name[16]; // file name (null-padded) tb_char_t date[12]; // modification time (decimal) tb_char_t uid[6]; // user ID (decimal) tb_char_t gid[6]; // group ID (decimal) tb_char_t mode[8]; // file mode (octal) tb_char_t size[10]; // file size (decimal) tb_char_t fmag[2]; // magic: "`\n" } __tb_packed__ xm_ar_header_t; #include "tbox/prefix/packed.h" /* ////////////////////////////////////////////////////////////////////////////////////// * inline implementation */ /* parse decimal number from string * * @param str the string * @param len the length * @return the parsed number, or -1 on error */ static __tb_inline__ tb_int64_t xm_binutils_ar_parse_decimal(tb_char_t const *str, tb_size_t len) { tb_assert_and_check_return_val(str && len > 0, -1); tb_int64_t result = 0; for (tb_size_t i = 0; i < len; i++) { if (str[i] == ' ' || str[i] == '\0') { break; } if (str[i] < '0' || str[i] > '9') { return -1; } result = result * 10 + (str[i] - '0'); } return result; } /* get member name from AR header, handling extended names (#N/L format) * * @param istream the input stream * @param header the AR header * @param name output buffer for the name * @param name_size size of the name buffer * @param name_len output: actual name length * @param bytes_read output: total bytes read from stream (including newline, for extended names) * @return tb_true on success, tb_false on failure */ static __tb_inline__ tb_bool_t xm_binutils_ar_get_member_name(tb_stream_ref_t istream, xm_ar_header_t const* header, tb_char_t* name, tb_size_t name_size, tb_size_t* name_len, tb_hize_t* bytes_read) { tb_assert_and_check_return_val(istream && header && name && name_size > 0 && name_len && bytes_read, tb_false); *bytes_read = 0; /* check for extended name format (#N/L or #1/N) * In BSD AR format: * - #1/N means name is directly after header, N is total length (including name) * - #N/L means name length is N, total length is L * - #1/N can also mean name is in long name table at offset 1 * We'll try to read the name directly from stream first */ if (header->name[0] == '#') { // find the '/' separator tb_size_t slash_pos = 0; for (tb_size_t i = 1; i < 16; i++) { if (header->name[i] == '/') { slash_pos = i; break; } } if (slash_pos > 0 && slash_pos < 16) { // parse the number before '/' (could be name length or offset) tb_int64_t first_num = xm_binutils_ar_parse_decimal(header->name + 1, slash_pos - 1); // parse the number after '/' (total length) tb_int64_t total_length = xm_binutils_ar_parse_decimal(header->name + slash_pos + 1, 16 - slash_pos - 1); if (first_num <= 0 || total_length <= 0) { return tb_false; } /* In BSD AR format, extended name is directly after header * The name data starts immediately after the header, no newline * Read exactly total_length bytes for the name section */ tb_byte_t c; tb_size_t name_bytes = 0; tb_hize_t bytes_read_so_far = 0; // Read name characters until we hit null terminator or reach total_length while (bytes_read_so_far < (tb_hize_t)total_length && name_bytes < name_size - 1) { if (!tb_stream_bread(istream, &c, 1)) { return tb_false; } bytes_read_so_far++; if (c == '\0') { // Stop reading name at null terminator, but continue reading to reach total_length break; } // Include all characters in the name, including newlines if present name[name_bytes++] = (tb_char_t)c; } name[name_bytes] = '\0'; *name_len = name_bytes; // Skip remaining bytes to reach total_length (there may be padding or null terminators) if (bytes_read_so_far < (tb_hize_t)total_length) { tb_hize_t remaining_to_read = (tb_hize_t)total_length - bytes_read_so_far; if (!tb_stream_skip(istream, remaining_to_read)) { return tb_false; } } // Total bytes read = name + padding = total_length *bytes_read = (tb_hize_t)total_length; return tb_true; } } if (header->name[0] == '/') { if (header->name[1] == '/') { tb_strlcpy(name, "//", name_size); *name_len = 2; } else { tb_strlcpy(name, "/", name_size); *name_len = 1; } *bytes_read = 0; return tb_true; } // regular name (null-terminated or space-padded) tb_size_t i = 0; for (i = 0; i < 16 && i < name_size - 1; i++) { if (header->name[i] == ' ' || header->name[i] == '\0' || header->name[i] == '/') { break; } name[i] = header->name[i]; } name[i] = '\0'; *name_len = i; *bytes_read = 0; // Regular names are in header, not read from stream return tb_true; } /* check AR magic (!\n) * * @param istream the input stream * @param base_offset the base offset * @return tb_true on success, tb_false on failure */ static __tb_inline__ tb_bool_t xm_binutils_ar_check_magic(tb_stream_ref_t istream, tb_hize_t base_offset) { tb_uint8_t magic[8]; if (!tb_stream_seek(istream, base_offset)) { return tb_false; } if (!tb_stream_bread(istream, magic, 8)) { return tb_false; } if (magic[0] != '!' || magic[1] != '<' || magic[2] != 'a' || magic[3] != 'r' || magic[4] != 'c' || magic[5] != 'h' || (magic[6] != '>' && magic[6] != '\n') || (magic[7] != '\n' && magic[7] != '\r')) { return tb_false; } return tb_true; } /* check if member is a symbol table (should be skipped) * * @param name the member name * @return tb_true if it's a symbol table, tb_false otherwise */ static __tb_inline__ tb_bool_t xm_binutils_ar_is_symbol_table(tb_char_t const *name) { tb_assert_and_check_return_val(name, tb_false); return (tb_strcmp(name, "__.SYMDEF") == 0 || tb_strcmp(name, "__.SYMDEF SORTED") == 0 || tb_strcmp(name, "/") == 0 || tb_strcmp(name, "//") == 0 || tb_strncmp(name, "__.SYMDEF", 9) == 0); } /* check if member is an object file (based on extension) * * @param name the member name * @return tb_true if it's likely an object file, tb_false otherwise */ static __tb_inline__ tb_bool_t xm_binutils_ar_is_object_file(tb_char_t const *name) { tb_assert_and_check_return_val(name, tb_false); tb_size_t len = tb_strlen(name); if (len == 0) { return tb_false; } // check common object file extensions if (len >= 2 && name[len - 2] == '.' && name[len - 1] == 'o') { return tb_true; } if (len >= 4 && tb_strcmp(name + len - 4, ".obj") == 0) { return tb_true; } // check if it's a COFF/ELF/Mach-O file by detecting format // For now, we'll extract all non-symbol-table members return tb_true; } #endif