| /* |
| * Copyright (c) 2018 Rob Clark <robdclark@gmail.com> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| /* |
| * Decoder for "new" GL_OES_get_program_binary format. |
| * |
| * Overall structure is: |
| * |
| * - header at top, contains, amongst other things, offsets of |
| * per shader stage sections. |
| * - per shader stage section (shader_info) starts with a header, |
| * followed by a variably length list of descriptors. Each |
| * descriptor has a type/count/size plus offset from the start |
| * of shader_info section where the data is found |
| */ |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <stddef.h> |
| #include <fcntl.h> |
| #include <string.h> |
| |
| #include "redump.h" |
| #include "disasm.h" |
| #include "io.h" |
| #include "util.h" |
| |
| const char *infile; |
| static int dump_full = 0; |
| static int dump_offsets = 0; |
| static int gpu_id = 320; |
| static int shaderdb = 0; /* output shaderdb style traces to stderr */ |
| |
| struct state { |
| char *buf; |
| int sz; |
| int lvl; |
| |
| /* current shader_info section, some offsets calculated relative to |
| * this, rather than relative to start of buffer. |
| */ |
| void *shader; |
| |
| /* size of each entry within a shader_descriptor_blk: */ |
| int desc_size; |
| |
| const char *shader_type; |
| int full_regs; |
| int half_regs; |
| }; |
| |
| #define PACKED __attribute__((__packed__)) |
| |
| #define OFF(field) do { \ |
| if (dump_offsets) \ |
| printf("%08x: ", (uint32_t)((char *)&field - state->buf));\ |
| } while (0) |
| |
| /* decode field as hex */ |
| #define X(s, field) do { \ |
| OFF(s->field); \ |
| printf("%s%12s:\t0x%x\n", tab(state->lvl), #field, s->field); \ |
| } while (0) |
| |
| /* decode field as digit */ |
| #define D(s, field) do { \ |
| OFF(s->field); \ |
| printf("%s%12s:\t%u\n", tab(state->lvl), #field, s->field); \ |
| } while (0) |
| |
| /* decode field as float/hex */ |
| #define F(s, field) do { \ |
| OFF(s->field); \ |
| printf("%s%12s:\t%f (0x%0x)\n", tab(state->lvl), #field, \ |
| d2f(s->field), s->field); \ |
| } while (0) |
| |
| /* decode field as register: (type is 'r' or 'c') */ |
| #define R(s, field, type) do { \ |
| OFF(s->field); \ |
| printf("%s%12s:\t%c%u.%c\n", tab(state->lvl), #field, type, \ |
| (s->field >> 2), "xyzw"[s->field & 0x3]); \ |
| } while (0) |
| |
| /* decode inline string (presumably null terminated?) */ |
| #define S(s, field) do { \ |
| OFF(s->field); \ |
| printf("%s%12s:\t%s\n", tab(state->lvl), #field, s->field); \ |
| } while (0) |
| |
| /* decode string-table string */ |
| #define T(s, field) TODO |
| |
| /* decode field as unknown */ |
| #define U(s, start, end) \ |
| dump_unknown(state, s->unk_ ## start ## _ ## end, 0x ## start, (4 + 0x ## end - 0x ## start) / 4) |
| |
| /* decode field as offset to other section */ |
| #define O(s, field, type) do { \ |
| X(s, field); \ |
| assert(s->field < state->sz); \ |
| void *_p = &state->buf[s->field]; \ |
| state->lvl++; \ |
| decode_ ## type (state, _p); \ |
| state->lvl--; \ |
| } while (0) |
| |
| struct shader_info; |
| static void decode_shader_info(struct state *state, struct shader_info *info); |
| |
| static void dump_unknown(struct state *state, void *buf, unsigned start, unsigned n) |
| { |
| uint32_t *ptr = buf; |
| uint8_t *ascii = buf; |
| |
| for (unsigned i = 0; i < n; i++) { |
| uint32_t d = ptr[i]; |
| |
| if (dump_offsets) |
| printf("%08x:", (uint32_t)((char *)&ptr[i] - state->buf)); |
| |
| printf("%s %04x:\t%08x", tab(state->lvl), start + i * 4, d); |
| |
| printf("\t|"); |
| for (unsigned j = 0; j < 4; j++) { |
| uint8_t c = *(ascii++); |
| printf("%c", (isascii(c) && !iscntrl(c)) ? c : '.'); |
| } |
| printf("|\t%f", d2f(d)); |
| |
| /* TODO maybe scan for first non-null and non-ascii char starting from |
| * end of shader binary to (roughly) establish the start of the string |
| * table.. that would be a bit better filter for deciding if something |
| * might be a pointer into the string table. Also, the previous char |
| * to what it points to should probably be null. |
| */ |
| if ((d < state->sz) && |
| isascii(state->buf[d]) && |
| (strlen(&state->buf[d]) > 2) && |
| isascii(state->buf[d+1])) |
| printf("\t<== %s", &state->buf[d]); |
| |
| printf("\n"); |
| } |
| } |
| |
| struct PACKED header { |
| uint32_t version; /* I guess, always b10bcace ? */ |
| uint32_t unk_0004_0014[5]; |
| uint32_t size; |
| uint32_t size2; /* just to be sure? */ |
| uint32_t unk_0020_0020[1]; |
| uint32_t chksum; /* I guess? Small changes seem to result in big diffs here */ |
| uint32_t unk_0028_0050[11]; |
| uint32_t fs_info; /* offset of FS shader_info section */ |
| uint32_t unk_0058_0090[15]; |
| uint32_t vs_info; /* offset of VS shader_info section */ |
| uint32_t unk_0098_00b0[7]; |
| uint32_t vs_info2; /* offset of VS shader_info section (again?) */ |
| uint32_t unk_00b8_0110[23]; |
| uint32_t bs_info; /* offset of binning shader_info section */ |
| }; |
| |
| static void decode_header(struct state *state, struct header *hdr) |
| { |
| X(hdr, version); |
| U(hdr, 0004, 0014); |
| X(hdr, size); |
| X(hdr, size2); |
| U(hdr, 0020, 0020); |
| X(hdr, chksum); |
| U(hdr, 0028, 0050); |
| state->shader_type = "FRAG"; |
| O(hdr, fs_info, shader_info); |
| U(hdr, 0058, 0090); |
| state->shader_type = "VERT"; |
| O(hdr, vs_info, shader_info); |
| U(hdr, 0098, 00b0); |
| assert(hdr->vs_info == hdr->vs_info2); /* not sure what this if it is ever different */ |
| X(hdr, vs_info2); |
| U(hdr, 00b8, 0110); |
| state->shader_type = "BVERT"; |
| O(hdr, bs_info, shader_info); |
| |
| /* not sure how much of the rest of contents before start of fs_info |
| * is the header, vs other things.. just dump it all as unknown for |
| * now: |
| */ |
| dump_unknown(state, (void *)hdr + sizeof(*hdr), |
| sizeof(*hdr), (hdr->fs_info - sizeof(*hdr)) / 4); |
| } |
| |
| struct PACKED shader_entry_point { |
| /* entry point name, ie. "main" of TBD length, followed by unknown */ |
| char name[8]; |
| }; |
| |
| static void decode_shader_entry_point(struct state *state, |
| struct shader_entry_point *e) |
| { |
| S(e, name); |
| } |
| |
| struct PACKED shader_config { |
| uint32_t unk_0000_0008[3]; |
| uint32_t full_regs; |
| uint32_t half_regs; |
| }; |
| |
| static void decode_shader_config(struct state *state, struct shader_config *cfg) |
| { |
| U(cfg, 0000, 0008); |
| D(cfg, full_regs); |
| D(cfg, half_regs); |
| |
| state->full_regs = cfg->full_regs; |
| state->half_regs = cfg->half_regs; |
| |
| /* dump reset of unknown (size differs btwn versions) */ |
| dump_unknown(state, (void *)cfg + sizeof(*cfg), sizeof(*cfg), |
| (state->desc_size - sizeof(*cfg))/4); |
| } |
| |
| struct PACKED shader_io_block { |
| /* name of TBD length followed by unknown.. 42 dwords total */ |
| char name[20]; |
| uint32_t unk_0014_00a4[37]; |
| }; |
| |
| static void decode_shader_io_block(struct state *state, |
| struct shader_io_block *io) |
| { |
| S(io, name); |
| U(io, 0014, 00a4); |
| } |
| |
| struct PACKED shader_constant_block { |
| uint32_t value; |
| uint32_t unk_0004_000c[3]; |
| uint32_t regid; |
| uint32_t unk_0014_0024[5]; |
| }; |
| |
| static void decode_shader_constant_block(struct state *state, |
| struct shader_constant_block *c) |
| { |
| F(c, value); |
| U(c, 0004, 000c); |
| R(c, regid, 'c'); |
| U(c, 0014, 0024); |
| } |
| |
| enum { |
| ENTRY_POINT = 0, /* shader_entry_point */ |
| SHADER_CONFIG = 1, /* XXX placeholder name */ |
| SHADER_INPUT = 2, /* shader_io_block */ |
| SHADER_OUTPUT = 3, /* shader_io_block */ |
| CONSTANTS = 6, /* shader_constant_block */ |
| INTERNAL = 8, /* internal input, like bary.f coord */ |
| SHADER = 10, |
| } shader_info_block_type; |
| |
| /* Refers to location of some type of records, with an offset relative to |
| * start of shader_info block. |
| */ |
| struct PACKED shader_descriptor_block { |
| uint32_t type; /* block type */ |
| uint32_t offset; /* offset (relative to start of shader_info block) */ |
| uint32_t size; /* size in bytes */ |
| uint32_t count; /* number of records */ |
| uint32_t unk_0010_0010[1]; |
| }; |
| |
| static void decode_shader_descriptor_block(struct state *state, |
| struct shader_descriptor_block *blk) |
| { |
| D(blk, type); |
| X(blk, offset); |
| D(blk, size); |
| D(blk, count); |
| U(blk, 0010, 0010); |
| |
| /* offset relative to current shader block: */ |
| void *ptr = state->shader + blk->offset; |
| |
| if (blk->count == 0) { |
| assert(blk->size == 0); |
| } else { |
| assert((blk->size % blk->count) == 0); |
| } |
| |
| state->desc_size = blk->size / blk->count; |
| state->lvl++; |
| for (unsigned i = 0; i < blk->count; i++) { |
| switch (blk->type) { |
| case ENTRY_POINT: |
| printf("%sentry point %u:\n", tab(state->lvl-1), i); |
| decode_shader_entry_point(state, ptr); |
| break; |
| case SHADER_CONFIG: |
| printf("%sconfig %u:\n", tab(state->lvl-1), i); |
| decode_shader_config(state, ptr); |
| break; |
| case SHADER_INPUT: |
| printf("%sinput %u:\n", tab(state->lvl-1), i); |
| decode_shader_io_block(state, ptr); |
| break; |
| case SHADER_OUTPUT: |
| printf("%soutput %u:\n", tab(state->lvl-1), i); |
| decode_shader_io_block(state, ptr); |
| break; |
| case INTERNAL: |
| printf("%sinternal input %u:\n", tab(state->lvl-1), i); |
| decode_shader_io_block(state, ptr); |
| break; |
| case CONSTANTS: |
| printf("%sconstant %u:\n", tab(state->lvl-1), i); |
| decode_shader_constant_block(state, ptr); |
| break; |
| case SHADER: { |
| struct shader_stats stats; |
| printf("%sshader %u:\n", tab(state->lvl-1), i); |
| disasm_a3xx_stat(ptr, blk->size/4, state->lvl, stdout, gpu_id, &stats); |
| if (shaderdb) { |
| unsigned dwords = 2 * stats.instlen; |
| |
| if (gpu_id >= 400) { |
| dwords = ALIGN(dwords, 16 * 2); |
| } else { |
| dwords = ALIGN(dwords, 4 * 2); |
| } |
| |
| unsigned half_regs = state->half_regs; |
| unsigned full_regs = state->full_regs; |
| |
| /* On a6xx w/ merged/conflicting half and full regs, the |
| * full_regs footprint will be max of full_regs and half |
| * of half_regs.. we only care about which value is higher. |
| */ |
| if (gpu_id >= 600) { |
| /* footprint of half_regs in units of full_regs: */ |
| unsigned half_full = (half_regs + 1) / 2; |
| if (half_full > full_regs) |
| full_regs = half_full; |
| half_regs = 0; |
| } |
| |
| fprintf(stderr, |
| "%s shader: %u inst, %u nops, %u non-nops, %u dwords, " |
| "%u half, %u full, %u constlen, " |
| "%u (ss), %u (sy), %d max_sun, %d loops\n", |
| state->shader_type, stats.instructions, |
| stats.nops, stats.instructions - stats.nops, |
| dwords, half_regs, full_regs, |
| stats.constlen, stats.ss, stats.sy, |
| 0, 0); /* max_sun or loops not possible */ |
| } |
| /* this is a special case in a way, blk->count is # of |
| * instructions but disasm_a3xx() decodes all instructions, |
| * so just bail. |
| */ |
| i = blk->count; |
| break; |
| } |
| default: |
| dump_unknown(state, ptr, 0, state->desc_size/4); |
| break; |
| } |
| ptr += state->desc_size; |
| } |
| state->lvl--; |
| } |
| |
| /* there looks like one of these per shader, followed by "main" and |
| * some more info, and then the shader itself. |
| */ |
| struct PACKED shader_info { |
| uint32_t unk_0000_0010[5]; |
| uint32_t desc_off; /* offset to first descriptor block */ |
| uint32_t num_blocks; |
| }; |
| |
| static void decode_shader_info(struct state *state, struct shader_info *info) |
| { |
| assert((info->desc_off % 4) == 0); |
| |
| U(info, 0000, 0010); |
| X(info, desc_off); |
| D(info, num_blocks); |
| |
| dump_unknown(state, &info[1], 0, (info->desc_off - sizeof(*info))/4); |
| |
| state->shader = info; |
| |
| struct shader_descriptor_block *blocks = ((void *)info) + info->desc_off; |
| for (unsigned i = 0; i < info->num_blocks; i++) { |
| printf("%sdescriptor %u:\n", tab(state->lvl), i); |
| state->lvl++; |
| decode_shader_descriptor_block(state, &blocks[i]); |
| state->lvl--; |
| } |
| } |
| |
| static void dump_program(struct state *state) |
| { |
| struct header *hdr = (void *)state->buf; |
| |
| if (dump_full) |
| dump_unknown(state, state->buf, 0, state->sz/4); |
| |
| decode_header(state, hdr); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| enum rd_sect_type type = RD_NONE; |
| enum debug_t debug = PRINT_RAW | PRINT_STATS; |
| void *buf = NULL; |
| int sz; |
| struct io *io; |
| int raw_program = 0; |
| |
| /* lame argument parsing: */ |
| |
| while (1) { |
| if ((argc > 1) && !strcmp(argv[1], "--verbose")) { |
| debug |= PRINT_RAW | PRINT_VERBOSE; |
| argv++; |
| argc--; |
| continue; |
| } |
| if ((argc > 1) && !strcmp(argv[1], "--expand")) { |
| debug |= EXPAND_REPEAT; |
| argv++; |
| argc--; |
| continue; |
| } |
| if ((argc > 1) && !strcmp(argv[1], "--full")) { |
| /* only short dump, original shader, symbol table, and disassembly */ |
| dump_full = 1; |
| argv++; |
| argc--; |
| continue; |
| } |
| if ((argc > 1) && !strcmp(argv[1], "--dump-offsets")) { |
| dump_offsets = 1; |
| argv++; |
| argc--; |
| continue; |
| } |
| if ((argc > 1) && !strcmp(argv[1], "--raw")) { |
| raw_program = 1; |
| argv++; |
| argc--; |
| continue; |
| } |
| if ((argc > 1) && !strcmp(argv[1], "--shaderdb")) { |
| shaderdb = 1; |
| argv++; |
| argc--; |
| continue; |
| } |
| break; |
| } |
| |
| if (argc != 2) { |
| fprintf(stderr, "usage: pgmdump2 [--verbose] [--expand] [--full] [--dump-offsets] [--raw] [--shaderdb] testlog.rd\n"); |
| return -1; |
| } |
| |
| disasm_a3xx_set_debug(debug); |
| |
| infile = argv[1]; |
| |
| io = io_open(infile); |
| if (!io) { |
| fprintf(stderr, "could not open: %s\n", infile); |
| return -1; |
| } |
| |
| if (raw_program) |
| { |
| io_readn(io, &sz, 4); |
| free(buf); |
| |
| /* note: allow hex dumps to go a bit past the end of the buffer.. |
| * might see some garbage, but better than missing the last few bytes.. |
| */ |
| buf = calloc(1, sz + 3); |
| io_readn(io, buf + 4, sz); |
| (*(int*)buf) = sz; |
| |
| struct state state = { |
| .buf = buf, |
| .sz = sz, |
| }; |
| printf("############################################################\n"); |
| printf("program:\n"); |
| dump_program(&state); |
| printf("############################################################\n"); |
| return 0; |
| } |
| |
| /* figure out what sort of input we are dealing with: */ |
| if (!(check_extension(infile, ".rd") || check_extension(infile, ".rd.gz"))) { |
| int ret; |
| buf = calloc(1, 100 * 1024); |
| ret = io_readn(io, buf, 100 * 1024); |
| if (ret < 0) { |
| fprintf(stderr, "error: %m"); |
| return -1; |
| } |
| return disasm_a3xx(buf, ret/4, 0, stdout, gpu_id); |
| } |
| |
| while ((io_readn(io, &type, sizeof(type)) > 0) && (io_readn(io, &sz, 4) > 0)) { |
| free(buf); |
| |
| /* note: allow hex dumps to go a bit past the end of the buffer.. |
| * might see some garbage, but better than missing the last few bytes.. |
| */ |
| buf = calloc(1, sz + 3); |
| io_readn(io, buf, sz); |
| |
| switch(type) { |
| case RD_TEST: |
| if (dump_full) |
| printf("test: %s\n", (char *)buf); |
| break; |
| case RD_VERT_SHADER: |
| printf("vertex shader:\n%s\n", (char *)buf); |
| break; |
| case RD_FRAG_SHADER: |
| printf("fragment shader:\n%s\n", (char *)buf); |
| break; |
| case RD_PROGRAM: { |
| struct state state = { |
| .buf = buf, |
| .sz = sz, |
| }; |
| printf("############################################################\n"); |
| printf("program:\n"); |
| dump_program(&state); |
| printf("############################################################\n"); |
| break; |
| } |
| case RD_GPU_ID: |
| gpu_id = *((unsigned int *)buf); |
| printf("gpu_id: %d\n", gpu_id); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| io_close(io); |
| |
| return 0; |
| } |