summaryrefslogtreecommitdiff
path: root/core/src/xmake/binutils/mslib/readsyms.c
blob: e314d08f38aeabdd4c5b398fb2cc03e56304f4cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*!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        readsyms.c
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME "mslib_readsyms"
#define TB_TRACE_MODULE_DEBUG (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "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);

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
static tb_bool_t xm_binutils_mslib_parse_archive_symbols(tb_stream_ref_t istream, tb_hize_t member_size, lua_State* lua, int map_idx) {
    // try to parse as Second Linker Member (LE)
    tb_hize_t start_pos = tb_stream_offset(istream);
    tb_uint32_t* offsets = tb_null;
    tb_uint16_t* indices = tb_null;
    tb_char_t* string_table = tb_null;
    tb_bool_t ok = tb_false;

    do {
        // read number of members
        tb_uint32_t num_members = 0;
        if (!tb_stream_bread_u32_le(istream, &num_members)) {
            break;
        }

        // sanity check
        if (num_members == 0 || num_members > 65536 || num_members * 4 >= member_size) {
            break;
        }

        // read offsets
        offsets = tb_nalloc_type(num_members, tb_uint32_t);
        tb_check_break(offsets);

        tb_size_t i;
        for (i = 0; i < num_members; i++) {
            if (!tb_stream_bread_u32_le(istream, &offsets[i])) {
                break;
            }
        }
        if (i < num_members) {
            break;
        }

        // read number of symbols
        tb_uint32_t num_symbols = 0;
        if (!tb_stream_bread_u32_le(istream, &num_symbols)) {
            break;
        }

        if (num_symbols == 0 || num_symbols > 1000000) {
            break;
        }

        // read indices
        indices = tb_nalloc_type(num_symbols, tb_uint16_t);
        tb_check_break(indices);

        for (i = 0; i < num_symbols; i++) {
            if (!tb_stream_bread_u16_le(istream, &indices[i])) {
                break;
            }
        }
        if (i < num_symbols) {
            break;
        }

        // read string table
        tb_hize_t current = tb_stream_offset(istream);
        tb_hize_t string_table_size = member_size - (current - start_pos);

        string_table = (tb_char_t*)tb_malloc_bytes((tb_size_t)string_table_size);
        tb_check_break(string_table);

        if (!tb_stream_bread(istream, (tb_byte_t*)string_table, (tb_size_t)string_table_size)) {
            break;
        }

        // populate map
        tb_char_t* p = string_table;
        tb_char_t* end = string_table + string_table_size;

        for (i = 0; i < num_symbols; i++) {
            if (p >= end) {
                break;
            }

            tb_char_t* sym_name = p;
            tb_size_t sym_len = tb_strlen(sym_name);
            p += sym_len + 1;

            tb_uint16_t idx = indices[i];
            if (idx > 0 && idx <= num_members) {
                tb_uint32_t offset = offsets[idx - 1];

                lua_pushinteger(lua, offset);
                lua_rawget(lua, map_idx);
                if (lua_isnil(lua, -1)) {
                    lua_pop(lua, 1);
                    lua_newtable(lua);
                    lua_pushinteger(lua, offset);
                    lua_pushvalue(lua, -2);
                    lua_rawset(lua, map_idx);
                }
                int count = (int)lua_objlen(lua, -1);
                lua_pushstring(lua, sym_name);
                lua_rawseti(lua, -2, count + 1);
                lua_pop(lua, 1); // pop list
            }
        }
        ok = tb_true;

    } while (0);

    if (offsets) {
        tb_free(offsets);
    }
    if (indices) {
        tb_free(indices);
    }
    if (string_table) {
        tb_free(string_table);
    }

    if (!ok) {
        tb_stream_seek(istream, start_pos);
    }
    return ok;
}

/* read symbols from MSVC lib archive
 *
 * @param istream     the input stream
 * @param base_offset the base offset
 * @param lua         the lua state
 * @return            tb_true on success, tb_false on failure
 */
tb_bool_t xm_binutils_mslib_read_symbols(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State* lua) {
    tb_assert_and_check_return_val(istream && lua, tb_false);

    // check magic (!<arch>\n)
    if (!xm_binutils_mslib_check_magic(istream)) {
        return tb_false;
    }

    // create map table (offset -> symbols)
    lua_newtable(lua);
    int map_idx = lua_gettop(lua);

    tb_bool_t ok = tb_true;
    tb_size_t object_count = 0;
    tb_char_t* longnames = tb_null;
    tb_size_t  longnames_size = 0;

    // iterate through members
    while (ok) {
        // read header
        xm_mslib_header_t header;
        if (!tb_stream_bread(istream, (tb_byte_t*)&header, sizeof(header))) {
            // end of file
            break;
        }

        // parse member size
        tb_int64_t member_size = xm_binutils_mslib_parse_decimal(header.size, 10);
        if (member_size < 0) {
            ok = tb_false;
            break;
        }

        // parse member name
        tb_char_t member_name[256] = {0};
        tb_bool_t is_longname_table = tb_false;

        if (header.name[0] == '/') {
            if (header.name[1] == '/') {
                // long name table (//)
                is_longname_table = tb_true;
            } else if (tb_isdigit(header.name[1])) {
                // offset into long name table (/123)
                tb_int64_t offset = xm_binutils_mslib_parse_decimal(header.name + 1, 15);
                if (offset >= 0 && (tb_size_t)offset < longnames_size) {
                    /* copy from longnames
                     * names in longnames are null-terminated
                     */
                    tb_strlcpy(member_name, longnames + offset, sizeof(member_name));
                }
            } else {
                 /* symbol table or other special member (/)
                  * usually symbol table is just "/"
                  */
                 tb_strlcpy(member_name, "/", sizeof(member_name));
            }
        } else {
             // short name, ends with /
             tb_size_t i = 0;
             for (i = 0; i < 16 && header.name[i] != '/'; i++) {
                 member_name[i] = header.name[i];
             }
             member_name[i] = '\0';
        }

        if (is_longname_table) {
            longnames = (tb_char_t*)tb_ralloc(longnames, (tb_size_t)member_size + 1);
            if (!longnames || !tb_stream_bread(istream, (tb_byte_t*)longnames, (tb_size_t)member_size)) {
                ok = tb_false;
                break;
            }
            longnames[member_size] = '\0';
            longnames_size = (tb_size_t)member_size;

            // align
            if (member_size % 2) {
                if (!tb_stream_skip(istream, 1)) {
                    ok = tb_false;
                    break;
                }
            }
            continue;
        }

        // check if we should process
        /* skip empty names, long name table (//) - handled above,
         * and __.SYMDEF (SysV/BSD style symbol table, just in case)
         */
        if (member_name[0] == '\0' || tb_strcmp(member_name, "//") == 0 ||
            tb_strncmp(member_name, "__.SYMDEF", 9) == 0) {

            // skip member data
            if (!tb_stream_skip(istream, member_size)) {
                ok = tb_false;
                break;
            }

             // align
            if (member_size % 2) {
                 if (!tb_stream_skip(istream, 1)) {
                    ok = tb_false;
                    break;
                }
            }
            continue;
        }

        if (tb_strcmp(member_name, "/") == 0) {
             // try to parse archive symbols
             if (!xm_binutils_mslib_parse_archive_symbols(istream, (tb_hize_t)member_size, lua, map_idx)) {
                 // if failed, skip member data
                 if (!tb_stream_skip(istream, member_size)) {
                    ok = tb_false;
                    break;
                 }
             }
             // align
             if (member_size % 2) {
                  if (!tb_stream_skip(istream, 1)) {
                     ok = tb_false;
                     break;
                 }
             }
             continue;
        }

        // save current position
        tb_hize_t current_pos = tb_stream_offset(istream);
        tb_hize_t header_offset = current_pos - sizeof(xm_mslib_header_t);

        // detect format
        tb_int_t format = xm_binutils_format_detect(istream);
        if (format != XM_BINUTILS_FORMAT_UNKNOWN && format != XM_BINUTILS_FORMAT_AR) {
            // create entry table
            lua_newtable(lua);

            // object name
            lua_pushstring(lua, "objectfile");
            lua_pushstring(lua, member_name);
            lua_settable(lua, -3);

            // symbols
            lua_pushstring(lua, "symbols");
            tb_bool_t read_ok = tb_false;
            if (format == XM_BINUTILS_FORMAT_COFF) {
                read_ok = xm_binutils_coff_read_symbols(istream, current_pos, lua);
            } else if (format == XM_BINUTILS_FORMAT_ELF) {
                read_ok = xm_binutils_elf_read_symbols(istream, current_pos, lua);
            } else if (format == XM_BINUTILS_FORMAT_MACHO) {
                read_ok = xm_binutils_macho_read_symbols(istream, current_pos, lua);
            }

            // if read failed or empty, try map
            tb_bool_t has_symbols = tb_false;
            if (read_ok) {
                 if (lua_objlen(lua, -1) > 0) {
                     has_symbols = tb_true;
                 } else {
                     lua_pop(lua, 1); // pop empty table
                 }
            }

            if (!has_symbols) {
                // check map
                lua_pushinteger(lua, (lua_Integer)header_offset);
                lua_rawget(lua, map_idx);
                if (lua_istable(lua, -1)) {
                    // convert list of names to list of {name=..., type="global"}
                    lua_newtable(lua); // result table
                    int count = (int)lua_objlen(lua, -2);
                    for (int i = 1; i <= count; i++) {
                        lua_rawgeti(lua, -2, i);
                        const char* name = lua_tostring(lua, -1);
                        if (name) {
                            lua_newtable(lua);
                            lua_pushstring(lua, "name");
                            lua_pushstring(lua, name);
                            lua_settable(lua, -3);

                            lua_pushstring(lua, "type");
                            lua_pushstring(lua, "T");
                            lua_settable(lua, -3);

                            lua_rawseti(lua, -3, i);
                        }
                        lua_pop(lua, 1); // pop name
                    }
                    lua_remove(lua, -2); // remove map entry list
                    has_symbols = tb_true;
                } else {
                    lua_pop(lua, 1); // pop nil
                }
            }

            if (has_symbols) {
                lua_settable(lua, -3);
                lua_rawseti(lua, map_idx - 1, (int)(++object_count));
            } else {
                lua_pop(lua, 2); // pop symbols key and entry table
            }
        }

        // skip to next member
        tb_hize_t member_data_read = tb_stream_offset(istream) - current_pos;
        tb_hize_t remaining_size = (tb_hize_t)member_size - member_data_read;

        if (remaining_size > 0) {
            if (!tb_stream_skip(istream, remaining_size)) {
                ok = tb_false;
                break;
            }
        } else if (remaining_size < 0) {
             if (!tb_stream_seek(istream, current_pos + (tb_hize_t)member_size)) {
                ok = tb_false;
                break;
             }
        }

        // align to 2-byte boundary
        if (member_size % 2) {
             if (!tb_stream_skip(istream, 1)) {
                ok = tb_false;
                break;
            }
        }
    }

    if (longnames) {
        tb_free(longnames);
    }
    lua_remove(lua, map_idx);
    return ok;
}