| /* Disassembler for RISC-V. |
| Copyright (C) 2019 Red Hat, Inc. |
| This file is part of elfutils. |
| Written by Ulrich Drepper <drepper@redhat.com>, 2019. |
| |
| This file is free software; you can redistribute it and/or modify |
| it under the terms of either |
| |
| * the GNU Lesser General Public License as published by the Free |
| Software Foundation; either version 3 of the License, or (at |
| your option) any later version |
| |
| or |
| |
| * the GNU General Public License as published by the Free |
| Software Foundation; either version 2 of the License, or (at |
| your option) any later version |
| |
| or both in parallel, as here. |
| |
| elfutils is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received copies of the GNU General Public License and |
| the GNU Lesser General Public License along with this program. If |
| not, see <http://www.gnu.org/licenses/>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "../libebl/libeblP.h" |
| |
| #define MACHINE_ENCODING __LITTLE_ENDIAN |
| #include "memory-access.h" |
| |
| |
| #define ADD_CHAR(ch) \ |
| do { \ |
| if (unlikely (bufcnt == bufsize)) \ |
| goto enomem; \ |
| buf[bufcnt++] = (ch); \ |
| } while (0) |
| |
| #define ADD_STRING(str) \ |
| do { \ |
| const char *_str0 = (str); \ |
| size_t _len0 = strlen (_str0); \ |
| ADD_NSTRING (_str0, _len0); \ |
| } while (0) |
| |
| #define ADD_NSTRING(str, len) \ |
| do { \ |
| const char *_str = (str); \ |
| size_t _len = (len); \ |
| if (unlikely (bufcnt + _len > bufsize)) \ |
| goto enomem; \ |
| memcpy (buf + bufcnt, _str, _len); \ |
| bufcnt += _len; \ |
| } while (0) |
| |
| |
| static const char *regnames[32] = |
| { |
| "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", |
| "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", |
| "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", |
| "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" |
| }; |
| #define REG(nr) ((char *) regnames[nr]) |
| #define REGP(nr) REG (8 + (nr)) |
| |
| |
| static const char *fregnames[32] = |
| { |
| "ft0", "ft1", "ft2", "ft3", "ft4", "ft5", "ft6", "ft7", |
| "fs0", "fs1", "fa0", "fa1", "fa2", "fa3", "fa4", "fa5", |
| "fa6", "fa7", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", |
| "fs8", "fs9", "fs10", "fs11", "ft8", "ft9", "ft10", "ft11" |
| }; |
| #define FREG(nr) ((char *) fregnames[nr]) |
| #define FREGP(nr) FREG (8 + (nr)) |
| |
| |
| struct known_csrs |
| { |
| uint16_t nr; |
| const char *name; |
| }; |
| |
| static int compare_csr (const void *a, const void *b) |
| { |
| const struct known_csrs *ka = (const struct known_csrs *) a; |
| const struct known_csrs *kb = (const struct known_csrs *) b; |
| if (ka->nr < kb->nr) |
| return -1; |
| return ka->nr == kb->nr ? 0 : 1; |
| } |
| |
| |
| int |
| riscv_disasm (Ebl *ebl, |
| const uint8_t **startp, const uint8_t *end, GElf_Addr addr, |
| const char *fmt, DisasmOutputCB_t outcb, |
| DisasmGetSymCB_t symcb __attribute__((unused)), |
| void *outcbarg, void *symcbarg __attribute__((unused))) |
| { |
| const char *const save_fmt = fmt; |
| |
| #define BUFSIZE 512 |
| char initbuf[BUFSIZE]; |
| size_t bufcnt; |
| size_t bufsize = BUFSIZE; |
| char *buf = initbuf; |
| |
| int retval = 0; |
| while (1) |
| { |
| const uint8_t *data = *startp; |
| assert (data <= end); |
| if (data + 2 > end) |
| { |
| if (data != end) |
| retval = -1; |
| break; |
| } |
| uint16_t first = read_2ubyte_unaligned (data); |
| |
| // Determine length. |
| size_t length; |
| if ((first & 0x3) != 0x3) |
| length = 2; |
| else if ((first & 0x1f) != 0x1f) |
| length = 4; |
| else if ((first & 0x3f) != 0x3f) |
| length = 6; |
| else if ((first & 0x7f) != 0x7f) |
| length = 8; |
| else |
| { |
| uint16_t nnn = (first >> 12) & 0x7; |
| if (nnn != 0x7) |
| length = 10 + 2 * nnn; |
| else |
| // This is invalid as of the RISC-V spec on 2019-06-21. |
| // The instruction is at least 192 bits in size so use |
| // this minimum size. |
| length = 24; |
| } |
| if (data + length > end) |
| { |
| retval = -1; |
| break; |
| } |
| |
| char *mne = NULL; |
| char mnebuf[32]; |
| char *op[5] = { NULL, NULL, NULL, NULL, NULL }; |
| char immbuf[32]; |
| size_t len; |
| char *strp = NULL; |
| char addrbuf[32]; |
| bufcnt = 0; |
| int64_t opaddr; |
| if (length == 2) |
| { |
| size_t idx = (first >> 13) * 3 + (first & 0x3); |
| switch (idx) |
| { |
| uint16_t rd; |
| uint16_t rs1; |
| uint16_t rs2; |
| |
| case 0: |
| if ((first & 0x1fe0) != 0) |
| { |
| mne = "addi"; |
| op[0] = REGP ((first & 0x1c) >> 2); |
| op[1] = REG (2); |
| opaddr = (((first >> 1) & 0x3c0) |
| | ((first >> 7) & 0x30) |
| | ((first >> 2) & 0x8) |
| | ((first >> 4) & 0x4)); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRIu64, opaddr); |
| op[2] = addrbuf; |
| } |
| else if (first == 0) |
| mne = "unimp"; |
| break; |
| case 1: |
| rs1 = (first >> 7) & 0x1f; |
| int16_t nzimm = ((0 - ((first >> 7) & 0x20)) |
| | ((first >> 2) & 0x1f)); |
| if (rs1 == 0) |
| mne = nzimm == 0 ? "nop" : "c.nop"; |
| else |
| { |
| mne = nzimm == 0 ? "c.addi" : "addi"; |
| op[0] = op[1] = REG (rs1); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId16, nzimm); |
| op[2] = addrbuf; |
| } |
| break; |
| case 2: |
| rs1 = (first >> 7) & 0x1f; |
| op[0] = op[1] = REG (rs1); |
| opaddr = ((first >> 7) & 0x20) | ((first >> 2) & 0x1f); |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr); |
| op[2] = addrbuf; |
| mne = rs1 == 0 ? "c.slli" : "slli"; |
| break; |
| case 3: |
| op[0] = FREGP ((first >> 2) & 0x7); |
| opaddr = ((first << 1) & 0xc0) | ((first >> 7) & 0x38); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRIu64 "(%s)", |
| opaddr, REGP ((first >> 7) & 0x7)); |
| op[1] = addrbuf; |
| mne = "fld"; |
| break; |
| case 4: |
| if (ebl->class == ELFCLASS32) |
| { |
| mne = "jal"; |
| opaddr = (((first << 3) & 0x20) | ((first >> 2) & 0xe) |
| | ((first << 1) & 0x80) | ((first >> 1) | 0x40) |
| | ((first << 2) & 0x400) | (first & 0xb00) |
| | ((first >> 6) & 0x10)); |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr); |
| op[0] = addrbuf; |
| } |
| else |
| { |
| int32_t imm = (((UINT32_C (0) - ((first >> 12) & 0x1)) << 5) |
| | ((first >> 2) & 0x1f)); |
| uint16_t reg = (first >> 7) & 0x1f; |
| if (reg == 0) |
| { |
| // Reserved |
| len = snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx16, first); |
| strp = addrbuf; |
| } |
| else |
| { |
| if (imm == 0) |
| mne = "sext.w"; |
| else |
| { |
| mne = "addiw"; |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId32, imm); |
| op[2] = addrbuf; |
| } |
| op[0] = op[1] = REG (reg); |
| } |
| } |
| break; |
| case 5: |
| op[0] = FREG ((first >> 7) & 0x1f); |
| opaddr = ((first << 4) & 0x1c0) | ((first >> 7) & 0x20) | ((first >> 2) & 0x18); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRIu64 "(%s)", opaddr, REG (2)); |
| op[1] = addrbuf; |
| mne = "fld"; |
| break; |
| case 6: |
| case 18: |
| mne = idx == 6 ? "lw" : "sw"; |
| op[0] = REGP ((first >> 2) & 0x7); |
| opaddr = (((first >> 7) & 0x38) | ((first << 1) & 0x40) |
| | ((first >> 4) & 0x4)); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", |
| opaddr, REGP ((first >> 7) & 0x7)); |
| op[1] = addrbuf; |
| break; |
| case 7: |
| mne = (first & 0xf80) == 0 ? "c.li" : "li"; |
| op[0] = REG((first >> 7) & 0x1f); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId16, |
| (UINT16_C (0) - ((first >> 7) & 0x20)) | ((first >> 2) & 0x1f)); |
| op[1] = addrbuf; |
| break; |
| case 8: |
| rd = ((first >> 7) & 0x1f); |
| if (rd == 0) |
| { |
| len = snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx16, first); |
| strp = addrbuf; |
| } |
| else |
| { |
| uint16_t uimm = (((first << 4) & 0xc0) |
| | ((first >> 7) & 0x20) |
| | ((first >> 2) & 0x1c)); |
| mne = "lw"; |
| op[0] = REG (rd); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRIu16 "(%s)", uimm, REG (2)); |
| op[1] = addrbuf; |
| } |
| break; |
| case 9: |
| if (ebl->class == ELFCLASS32) |
| { |
| mne = "flw"; |
| op[0] = FREGP ((first >> 2) & 0x7); |
| opaddr = (((first << 1) & 0x40) |
| | ((first >> 7) & 0x38) |
| | ((first >> 4) & 0x4)); |
| } |
| else |
| { |
| mne = "ld"; |
| op[0] = REGP ((first >> 2) & 0x7); |
| opaddr = ((first >> 7) & 0x38) | ((first << 1) & 0xc0); |
| } |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", |
| opaddr, REGP ((first >> 7) & 0x7)); |
| op[1] = addrbuf; |
| break; |
| case 10: |
| if ((first & 0xf80) == (2 << 7)) |
| { |
| mne = "addi"; |
| op[0] = op[1] = REG (2); |
| opaddr = (((first >> 2) & 0x10) | ((first << 3) & 0x20) |
| | ((first << 1) & 0x40) | ((first << 4) & 0x180) |
| | ((UINT64_C (0) - ((first >> 12) & 0x1)) << 9)); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64, opaddr); |
| op[2] = addrbuf; |
| } |
| else |
| { |
| mne = "lui"; |
| op[0] = REG((first & 0xf80) >> 7); |
| opaddr = (((UINT64_C (0) - ((first >> 12) & 0x1)) & ~0x1f) |
| | ((first >> 2) & 0x1f)); |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr & 0xfffff); |
| op[1] = addrbuf; |
| } |
| break; |
| case 11: |
| if (ebl->class == ELFCLASS32) |
| { |
| mne = "flw"; |
| op[0] = FREG ((first >> 7) & 0x1f); |
| opaddr = (((first << 4) & 0xc0) |
| | ((first >> 7) & 0x20) |
| | ((first >> 2) & 0x1c)); |
| } |
| else |
| { |
| mne = "ld"; |
| op[0] = REG ((first >> 7) & 0x1f); |
| opaddr = (((first << 4) & 0x1c0) |
| | ((first >> 7) & 0x20) |
| | ((first >> 2) & 0x18)); |
| } |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", opaddr, REG (2)); |
| op[1] = addrbuf; |
| break; |
| case 13: |
| if ((first & 0xc00) != 0xc00) |
| { |
| int16_t imm = ((first >> 7) & 0x20) | ((first >> 2) & 0x1f); |
| if ((first & 0xc00) == 0x800) |
| { |
| imm |= 0 - (imm & 0x20); |
| mne = "andi"; |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId16, imm); |
| } |
| else |
| { |
| if (ebl->class != ELFCLASS32 || imm < 32) |
| { |
| mne = (first & 0x400) ? "srai" : "srli"; |
| if (imm == 0) |
| { |
| strcpy (stpcpy (mnebuf, "c."), mne); |
| mne = mnebuf; |
| } |
| } |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx16, imm); |
| } |
| op[2] = addrbuf; |
| } |
| else |
| { |
| op[2] = REGP ((first >> 2) & 0x7); |
| static const char *const arithmne[8] = |
| { |
| "sub", "xor", "or", "and", "subw", "addw", NULL, NULL |
| }; |
| mne = (char *) arithmne[((first >> 10) & 0x4) | ((first >> 5) & 0x3)]; |
| } |
| op[0] = op[1] = REGP ((first >> 7) & 0x7); |
| break; |
| case 14: |
| rs1 = (first >> 7) & 0x1f; |
| rs2 = (first >> 2) & 0x1f; |
| op[0] = REG (rs1); |
| if ((first & 0x1000) == 0) |
| { |
| if (rs2 == 0) |
| { |
| op[1] = NULL; |
| if (rs1 == 1) |
| { |
| mne = "ret"; |
| op[0] = NULL; |
| } |
| else |
| mne = "jr"; |
| } |
| else |
| { |
| mne = rs1 != 0 ? "mv" : "c.mv"; |
| op[1] = REG (rs2); |
| } |
| } |
| else |
| { |
| if (rs2 == 0) |
| { |
| if (rs1 == 0) |
| { |
| mne = "ebreak"; |
| op[0] = op[1] = NULL; |
| } |
| else |
| mne = "jalr"; |
| } |
| else |
| { |
| mne = rs1 != 0 ? "add" : "c.add"; |
| op[2] = REG (rs2); |
| op[1] = op[0]; |
| } |
| } |
| break; |
| case 15: |
| op[0] = FREGP ((first >> 2) & 0x7); |
| opaddr = ((first << 1) & 0xc0) | ((first >> 7) & 0x38); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRIu64 "(%s)", |
| opaddr, REGP ((first >> 7) & 0x7)); |
| op[1] = addrbuf; |
| mne = "fsd"; |
| break; |
| case 16: |
| opaddr = (((UINT64_C (0) - ((first >> 12) & 0x1)) << 11) |
| | ((first << 2) & 0x400) |
| | ((first >> 1) & 0x300) |
| | ((first << 1) & 0x80) |
| | ((first >> 1) & 0x40) |
| | ((first << 3) & 0x20) |
| | ((first >> 7) & 0x10) |
| | ((first >> 2) & 0xe)); |
| mne = "j"; |
| // TODO translate address |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, addr + opaddr); |
| op[0] = addrbuf; |
| break; |
| case 17: |
| op[0] = FREG ((first >> 2) & 0x1f); |
| opaddr = ((first >> 1) & 0x1c0) | ((first >> 7) & 0x38); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRIu64 "(%s)", opaddr, REG (2)); |
| op[1] = addrbuf; |
| mne = "fsd"; |
| break; |
| case 19: |
| case 22: |
| mne = idx == 19 ? "beqz" : "bnez"; |
| op[0] = REG (8 + ((first >> 7) & 0x7)); |
| opaddr = addr + (((UINT64_C (0) - ((first >> 12) & 0x1)) & ~0xff) |
| | ((first << 1) & 0xc0) | ((first << 3) & 0x20) |
| | ((first >> 7) & 0x18) | ((first >> 2) & 0x6)); |
| // TODO translate address |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr); |
| op[1] = addrbuf; |
| break; |
| case 20: |
| op[0] = REG ((first >> 2) & 0x1f); |
| opaddr = ((first >> 1) & 0xc0) | ((first >> 7) & 0x3c); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", opaddr, REG (2)); |
| op[1] = addrbuf; |
| mne = "sw"; |
| break; |
| case 21: |
| if (idx == 18 || ebl->class == ELFCLASS32) |
| { |
| mne = "fsw"; |
| op[0] = FREGP ((first >> 2) & 0x7); |
| opaddr = (((first >> 7) & 0x38) | ((first << 1) & 0x40) |
| | ((first >> 4) & 0x4)); |
| } |
| else |
| { |
| mne = "sd"; |
| op[0] = REGP ((first >> 2) & 0x7); |
| opaddr = ((first >> 7) & 0x38) | ((first << 1) & 0xc0); |
| } |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", |
| opaddr, REGP ((first >> 7) & 0x7)); |
| op[1] = addrbuf; |
| break; |
| case 23: |
| if (idx == 18 || ebl->class == ELFCLASS32) |
| { |
| mne = "fsw"; |
| op[0] = FREG ((first & 0x7c) >> 2); |
| opaddr = ((first & 0x1e00) >> 7) | ((first & 0x180) >> 1); |
| } |
| else |
| { |
| mne = "sd"; |
| op[0] = REG ((first & 0x7c) >> 2); |
| opaddr = ((first & 0x1c00) >> 7) | ((first & 0x380) >> 1); |
| } |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", opaddr, REG (2)); |
| op[1] = addrbuf; |
| break; |
| default: |
| break; |
| } |
| |
| if (strp == NULL && mne == NULL) |
| { |
| len = snprintf (immbuf, sizeof (immbuf), "0x%04" PRIx16, first); |
| strp = immbuf; |
| } |
| } |
| else if (length == 4) |
| { |
| uint32_t word = read_4ubyte_unaligned (data); |
| size_t idx = (word >> 2) & 0x1f; |
| |
| switch (idx) |
| { |
| static const char widthchar[4] = { 's', 'd', '\0', 'q' }; |
| static const char intwidthchar[4] = { 'w', 'd', '\0', 'q' }; |
| static const char *const rndmode[8] = { "rne", "rtz", "rdn", "rup", "rmm", "???", "???", "dyn" }; |
| uint32_t rd; |
| uint32_t rs1; |
| uint32_t rs2; |
| uint32_t rs3; |
| uint32_t func; |
| |
| case 0x00: |
| case 0x01: |
| // LOAD and LOAD-FP |
| rd = (word >> 7) & 0x1f; |
| op[0] = idx == 0x00 ? REG (rd) : FREG (rd); |
| opaddr = ((int32_t) word) >> 20; |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", |
| opaddr, REG ((word >> 15) & 0x1f)); |
| op[1] = addrbuf; |
| func = (word >> 12) & 0x7; |
| static const char *const loadmne[8] = |
| { |
| "lb", "lh", "lw", "ld", "lbu", "lhu", "lwu", NULL |
| }; |
| static const char *const floadmne[8] = |
| { |
| NULL, NULL, "flw", "fld", "flq", NULL, NULL, NULL |
| }; |
| mne = (char *) (idx == 0x00 ? loadmne[func] : floadmne[func]); |
| break; |
| case 0x03: |
| // MISC-MEM |
| rd = (word >> 7) & 0x1f; |
| rs1 = (word >> 15) & 0x1f; |
| func = (word >> 12) & 0x7; |
| |
| if (word == 0x8330000f) |
| mne = "fence.tso"; |
| else if (word == 0x0000100f) |
| mne = "fence.i"; |
| else if (func == 0 && rd == 0 && rs1 == 0 && (word & 0xf0000000) == 0) |
| { |
| static const char *const order[16] = |
| { |
| "unknown", "w", "r", "rw", "o", "ow", "or", "orw", |
| "i", "iw", "ir", "irw", "io", "iow", "ior", "iorw" |
| }; |
| uint32_t pred = (word >> 20) & 0xf; |
| uint32_t succ = (word >> 24) & 0xf; |
| if (pred != 0xf || succ != 0xf) |
| { |
| op[0] = (char *) order[succ]; |
| op[1] = (char *) order[pred]; |
| } |
| mne = "fence"; |
| } |
| break; |
| case 0x04: |
| case 0x06: |
| // OP-IMM and OP-IMM32 |
| rd = (word >> 7) & 0x1f; |
| op[0] = REG (rd); |
| rs1 = (word >> 15) & 0x1f; |
| op[1] = REG (rs1); |
| opaddr = ((int32_t) word) >> 20; |
| static const char *const opimmmne[8] = |
| { |
| "addi", NULL, "slti", "sltiu", "xori", NULL, "ori", "andi" |
| }; |
| func = (word >> 12) & 0x7; |
| mne = (char *) opimmmne[func]; |
| if (mne == NULL) |
| { |
| const uint64_t shiftmask = ebl->class == ELFCLASS32 ? 0x1f : 0x3f; |
| if (func == 0x1 && (opaddr & ~shiftmask) == 0) |
| mne = "slli"; |
| else if (func == 0x5 && (opaddr & ~shiftmask) == 0) |
| mne = "srli"; |
| else if (func == 0x5 && (opaddr & ~shiftmask) == 0x400) |
| mne = "srai"; |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr & shiftmask); |
| op[2] = addrbuf; |
| } |
| else if (func == 0x0 && (rd != 0 || idx == 0x06) && rs1 == 0 && rd != 0) |
| { |
| mne = "li"; |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64, opaddr); |
| op[1] = addrbuf; |
| } |
| else if (func == 0x00 && opaddr == 0) |
| { |
| if (idx == 0x06) |
| mne ="sext."; |
| else if (rd == 0) |
| { |
| mne = "nop"; |
| op[0] = op[1] = NULL; |
| } |
| else |
| mne = "mv"; |
| } |
| else if (func == 0x3 && opaddr == 1) |
| mne = "seqz"; |
| else if (func == 0x4 && opaddr == -1) |
| { |
| mne = "not"; |
| op[2] = NULL; |
| } |
| else |
| { |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64, opaddr); |
| op[2] = addrbuf; |
| |
| if (func == 0x0 && rs1 == 0 && rd != 0) |
| { |
| op[1] = op[2]; |
| op[2] = NULL; |
| mne = "li"; |
| } |
| } |
| if (mne != NULL && idx == 0x06) |
| { |
| mne = strcpy (mnebuf, mne); |
| strcat (mnebuf, "w"); |
| } |
| break; |
| case 0x05: |
| case 0x0d: |
| // LUI and AUIPC |
| mne = idx == 0x05 ? "auipc" : "lui"; |
| op[0] = REG ((word >> 7) & 0x1f); |
| opaddr = word >> 12; |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr); |
| op[1] = addrbuf; |
| break; |
| case 0x08: |
| case 0x09: |
| // STORE and STORE-FP |
| rs2 = (word >> 20) & 0x1f; |
| op[0] = idx == 0x08 ? REG (rs2) : FREG (rs2); |
| opaddr = ((((int64_t) ((int32_t) word) >> 20)) & ~0x1f) | ((word >> 7) & 0x1f); |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", |
| opaddr, REG ((word >> 15) & 0x1f)); |
| op[1] = addrbuf; |
| func = (word >> 12) & 0x7; |
| static const char *const storemne[8] = |
| { |
| "sb", "sh", "sw", "sd", NULL, NULL, NULL, NULL |
| }; |
| static const char *const fstoremne[8] = |
| { |
| NULL, NULL, "fsw", "fsd", "fsq", NULL, NULL, NULL |
| }; |
| mne = (char *) (idx == 0x08 ? storemne[func] : fstoremne[func]); |
| break; |
| case 0x0b: |
| // AMO |
| op[0] = REG ((word >> 7) & 0x1f); |
| rs1 = (word >> 15) & 0x1f; |
| rs2 = (word >> 20) & 0x1f; |
| snprintf (addrbuf, sizeof (addrbuf), "(%s)", REG (rs1)); |
| op[2] = addrbuf; |
| size_t width = (word >> 12) & 0x7; |
| func = word >> 27; |
| static const char *const amomne[32] = |
| { |
| "amoadd", "amoswap", "lr", "sc", "amoxor", NULL, NULL, NULL, |
| "amoor", NULL, NULL, NULL, "amoand", NULL, NULL, NULL, |
| "amomin", NULL, NULL, NULL, "amomax", NULL, NULL, NULL, |
| "amominu", NULL, NULL, NULL, "amomaxu", NULL, NULL, NULL |
| }; |
| if (amomne[func] != NULL && width >= 2 && width <= 3 |
| && (func != 0x02 || rs2 == 0)) |
| { |
| if (func == 0x02) |
| { |
| op[1] = op[2]; |
| op[2] = NULL; |
| } |
| else |
| op[1] = REG (rs2); |
| |
| char *cp = stpcpy (mnebuf, amomne[func]); |
| *cp++ = '.'; |
| *cp++ = " wd "[width]; |
| assert (cp[-1] != ' '); |
| static const char *const aqrlstr[4] = |
| { |
| "", ".rl", ".aq", ".aqrl" |
| }; |
| strcpy (cp, aqrlstr[(word >> 25) & 0x3]); |
| mne = mnebuf; |
| } |
| break; |
| case 0x0c: |
| case 0x0e: |
| // OP and OP-32 |
| if ((word & 0xbc000000) == 0) |
| { |
| rs1 = (word >> 15) & 0x1f; |
| rs2 = (word >> 20) & 0x1f; |
| op[0] = REG ((word >> 7) & 0x1f); |
| func = ((word >> 21) & 0x10) | ((word >> 27) & 0x8) | ((word >> 12) & 0x7); |
| static const char *const arithmne2[32] = |
| { |
| "add", "sll", "slt", "sltu", "xor", "srl", "or", "and", |
| "sub", NULL, NULL, NULL, NULL, "sra", NULL, NULL, |
| "mul", "mulh", "mulhsu", "mulhu", "div", "divu", "rem", "remu", |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL |
| }; |
| static const char *const arithmne3[32] = |
| { |
| "addw", "sllw", NULL, NULL, NULL, "srlw", NULL, NULL, |
| "subw", NULL, NULL, NULL, NULL, "sraw", NULL, NULL, |
| "mulw", NULL, NULL, NULL, "divw", "divuw", "remw", "remuw", |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL |
| }; |
| if (func == 8 && rs1 == 0) |
| { |
| mne = idx == 0x0c ? "neg" : "negw"; |
| op[1] = REG (rs2); |
| } |
| else if (idx == 0x0c && rs2 == 0 && func == 2) |
| { |
| op[1] = REG (rs1); |
| mne = "sltz"; |
| } |
| else if (idx == 0x0c && rs1 == 0 && (func == 2 || func == 3)) |
| { |
| op[1] = REG (rs2); |
| mne = func == 2 ? "sgtz" : "snez"; |
| } |
| else |
| { |
| mne = (char *) (idx == 0x0c ? arithmne2[func] : arithmne3[func]); |
| op[1] = REG (rs1); |
| op[2] = REG (rs2); |
| } |
| } |
| break; |
| case 0x10: |
| case 0x11: |
| case 0x12: |
| case 0x13: |
| // MADD, MSUB, NMSUB, NMADD |
| if ((word & 0x06000000) != 0x04000000) |
| { |
| rd = (word >> 7) & 0x1f; |
| rs1 = (word >> 15) & 0x1f; |
| rs2 = (word >> 20) & 0x1f; |
| rs3 = (word >> 27) & 0x1f; |
| uint32_t rm = (word >> 12) & 0x7; |
| width = (word >> 25) & 0x3; |
| |
| static const char *const fmamne[4] = |
| { |
| "fmadd.", "fmsub.", "fnmsub.", "fnmadd." |
| }; |
| char *cp = stpcpy (mnebuf, fmamne[idx & 0x3]); |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| op[0] = FREG (rd); |
| op[1] = FREG (rs1); |
| op[2] = FREG (rs2); |
| op[3] = FREG (rs3); |
| if (rm != 0x7) |
| op[4] = (char *) rndmode[rm]; |
| } |
| break; |
| case 0x14: |
| // OP-FP |
| if ((word & 0x06000000) != 0x04000000) |
| { |
| width = (word >> 25) & 0x3; |
| rd = (word >> 7) & 0x1f; |
| rs1 = (word >> 15) & 0x1f; |
| rs2 = (word >> 20) & 0x1f; |
| func = word >> 27; |
| uint32_t rm = (word >> 12) & 0x7; |
| if (func < 4) |
| { |
| static const char *const fpop[4] = |
| { |
| "fadd", "fsub", "fmul", "fdiv" |
| }; |
| char *cp = stpcpy (mnebuf, fpop[func]); |
| *cp++ = '.'; |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| op[0] = FREG (rd); |
| op[1] = FREG (rs1); |
| op[2] = FREG (rs2); |
| if (rm != 0x7) |
| op[3] = (char *) rndmode[rm]; |
| } |
| else if (func == 0x1c && width != 2 && rs2 == 0 && rm <= 1) |
| { |
| char *cp; |
| if (rm == 0) |
| { |
| cp = stpcpy (mnebuf, "fmv.x."); |
| *cp++ = intwidthchar[width]; |
| } |
| else |
| { |
| cp = stpcpy (mnebuf, "fclass."); |
| *cp++ = widthchar[width]; |
| } |
| *cp = '\0'; |
| mne = mnebuf; |
| op[0] = REG (rd); |
| op[1] = FREG (rs1); |
| } |
| else if (func == 0x1e && width != 2 && rs2 == 0 && rm == 0) |
| { |
| char *cp = stpcpy (mnebuf, "fmv."); |
| *cp++ = intwidthchar[width]; |
| strcpy (cp, ".x"); |
| mne = mnebuf; |
| op[0] = FREG (rd); |
| op[1] = REG (rs1); |
| } |
| else if (func == 0x14) |
| { |
| uint32_t cmpop = (word >> 12) & 0x7; |
| if (cmpop < 3) |
| { |
| static const char *const mnefpcmp[3] = |
| { |
| "fle", "flt", "feq" |
| }; |
| char *cp = stpcpy (mnebuf, mnefpcmp[cmpop]); |
| *cp++ = '.'; |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| op[0] = REG (rd); |
| op[1] = FREG (rs1); |
| op[2] = FREG (rs2); |
| } |
| } |
| else if (func == 0x04) |
| { |
| uint32_t cmpop = (word >> 12) & 0x7; |
| if (cmpop < 3) |
| { |
| op[0] = FREG (rd); |
| op[1] = FREG (rs1); |
| |
| static const char *const mnefpcmp[3] = |
| { |
| "fsgnj.", "fsgnjn.", "fsgnjx." |
| }; |
| static const char *const altsignmne[3] = |
| { |
| "fmv.", "fneg.", "fabs." |
| }; |
| char *cp = stpcpy (mnebuf, rs1 == rs2 ? altsignmne[cmpop] : mnefpcmp[cmpop]); |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| |
| if (rs1 != rs2) |
| op[2] = FREG (rs2); |
| } |
| } |
| else if (func == 0x08 && width != 2 && rs2 <= 3 && rs2 != 2 && rs2 != width) |
| { |
| op[0] = FREG (rd); |
| op[1] = FREG (rs1); |
| char *cp = stpcpy (mnebuf, "fcvt."); |
| *cp++ = widthchar[width]; |
| *cp++ = '.'; |
| *cp++ = widthchar[rs2]; |
| *cp = '\0'; |
| mne = mnebuf; |
| } |
| else if ((func & 0x1d) == 0x18 && width != 2 && rs2 < 4) |
| { |
| char *cp = stpcpy (mnebuf, "fcvt."); |
| if (func == 0x18) |
| { |
| *cp++ = rs2 >= 2 ? 'l' : 'w'; |
| if ((rs2 & 1) == 1) |
| *cp++ = 'u'; |
| *cp++ = '.'; |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| op[0] = REG (rd); |
| op[1] = FREG (rs1); |
| } |
| else |
| { |
| *cp++ = widthchar[width]; |
| *cp++ = '.'; |
| *cp++ = rs2 >= 2 ? 'l' : 'w'; |
| if ((rs2 & 1) == 1) |
| *cp++ = 'u'; |
| *cp = '\0'; |
| op[0] = FREG (rd); |
| op[1] = REG (rs1); |
| } |
| mne = mnebuf; |
| if (rm != 0x7 && (func == 0x18 || width == 0 || rs2 >= 2)) |
| op[2] = (char *) rndmode[rm]; |
| } |
| else if (func == 0x0b && rs2 == 0) |
| { |
| op[0] = FREG (rd); |
| op[1] = FREG (rs1); |
| char *cp = stpcpy (mnebuf, "fsqrt."); |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| if (rm != 0x7) |
| op[2] = (char *) rndmode[rm]; |
| } |
| else if (func == 0x05 && rm < 2) |
| { |
| op[0] = FREG (rd); |
| op[1] = FREG (rs1); |
| op[2] = FREG (rs2); |
| char *cp = stpcpy (mnebuf, rm == 0 ? "fmin." : "fmax."); |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| } |
| else if (func == 0x14 && rm <= 0x2) |
| { |
| op[0] = REG (rd); |
| op[1] = FREG (rs1); |
| op[2] = FREG (rs2); |
| static const char *const fltcmpmne[3] = |
| { |
| "fle.", "flt.", "feq." |
| }; |
| char *cp = stpcpy (mnebuf, fltcmpmne[rm]); |
| *cp++ = widthchar[width]; |
| *cp = '\0'; |
| mne = mnebuf; |
| } |
| } |
| break; |
| case 0x18: |
| // BRANCH |
| rs1 = (word >> 15) & 0x1f; |
| op[0] = REG (rs1); |
| rs2 = (word >> 20) & 0x1f; |
| op[1] = REG (rs2); |
| opaddr = addr + (((UINT64_C (0) - (word >> 31)) << 12) |
| + ((word << 4) & 0x800) |
| + ((word >> 20) & 0x7e0) |
| + ((word >> 7) & 0x1e)); |
| // TODO translate address |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr); |
| op[2] = addrbuf; |
| static const char *const branchmne[8] = |
| { |
| "beq", "bne", NULL, NULL, "blt", "bge", "bltu", "bgeu" |
| }; |
| func = (word >> 12) & 0x7; |
| mne = (char *) branchmne[func]; |
| if (rs1 == 0 && func == 5) |
| { |
| op[0] = op[1]; |
| op[1] = op[2]; |
| op[2] = NULL; |
| mne = "blez"; |
| } |
| else if (rs1 == 0 && func == 4) |
| { |
| op[0] = op[1]; |
| op[1] = op[2]; |
| op[2] = NULL; |
| mne = "bgtz"; |
| } |
| else if (rs2 == 0) |
| { |
| if (func == 0 || func == 1 || func == 4 || func == 5) |
| { |
| op[1] = op[2]; |
| op[2] = NULL; |
| strcpy (stpcpy (mnebuf, mne), "z"); |
| mne = mnebuf; |
| } |
| } |
| else if (func == 5 || func == 7) |
| { |
| // binutils use these opcodes and the reverse parameter order |
| char *tmp = op[0]; |
| op[0] = op[1]; |
| op[1] = tmp; |
| mne = func == 5 ? "ble" : "bleu"; |
| } |
| break; |
| case 0x19: |
| // JALR |
| if ((word & 0x7000) == 0) |
| { |
| rd = (word >> 7) & 0x1f; |
| rs1 = (word >> 15) & 0x1f; |
| opaddr = (int32_t) word >> 20; |
| size_t next = 0; |
| if (rd > 1) |
| op[next++] = REG (rd); |
| if (opaddr == 0) |
| { |
| if (rs1 != 0 || next == 0) |
| op[next] = REG (rs1); |
| } |
| else |
| { |
| snprintf (addrbuf, sizeof (addrbuf), "%" PRId64 "(%s)", opaddr, REG (rs1)); |
| op[next] = addrbuf; |
| } |
| mne = rd == 0 ? "jr" : "jalr"; |
| } |
| break; |
| case 0x1b: |
| // JAL |
| rd = (word >> 7) & 0x1f; |
| if (rd != 0) |
| op[0] = REG (rd); |
| opaddr = addr + ((UINT64_C (0) - ((word >> 11) & 0x100000)) |
| | (word & 0xff000) |
| | ((word >> 9) & 0x800) |
| | ((word >> 20) & 0x7fe)); |
| // TODO translate address |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx64, opaddr); |
| op[rd != 0] = addrbuf; |
| mne = rd == 0 ? "j" : "jal"; |
| break; |
| case 0x1c: |
| // SYSTEM |
| rd = (word >> 7) & 0x1f; |
| rs1 = (word >> 15) & 0x1f; |
| if (word == 0x00000073) |
| mne = "ecall"; |
| else if (word == 0x00100073) |
| mne = "ebreak"; |
| else if (word == 0x00200073) |
| mne = "uret"; |
| else if (word == 0x10200073) |
| mne = "sret"; |
| else if (word == 0x30200073) |
| mne = "mret"; |
| else if (word == 0x10500073) |
| mne = "wfi"; |
| else if ((word & 0x3000) == 0x2000 && rs1 == 0) |
| { |
| uint32_t csr = word >> 20; |
| if (/* csr >= 0x000 && */ csr <= 0x007) |
| { |
| static const char *const unprivrw[4] = |
| { |
| NULL, "frflags", "frrm", "frsr", |
| }; |
| mne = (char *) unprivrw[csr - 0x000]; |
| } |
| else if (csr >= 0xc00 && csr <= 0xc03) |
| { |
| static const char *const unprivrolow[3] = |
| { |
| "rdcycle", "rdtime", "rdinstret" |
| }; |
| mne = (char *) unprivrolow[csr - 0xc00]; |
| } |
| op[0] = REG ((word >> 7) & 0x1f); |
| } |
| else if ((word & 0x3000) == 0x1000 && rd == 0) |
| { |
| uint32_t csr = word >> 20; |
| if (/* csr >= 0x000 && */ csr <= 0x003) |
| { |
| static const char *const unprivrs[4] = |
| { |
| NULL, "fsflags", "fsrm", "fssr", |
| }; |
| static const char *const unprivrsi[4] = |
| { |
| NULL, "fsflagsi", "fsrmi", NULL |
| }; |
| mne = (char *) ((word & 0x4000) == 0 ? unprivrs : unprivrsi)[csr - 0x000]; |
| |
| if ((word & 0x4000) == 0) |
| op[0] = REG ((word >> 15) & 0x1f); |
| else |
| { |
| snprintf (immbuf, sizeof (immbuf), "%" PRIu32, (word >> 15) & 0x1f); |
| op[0] = immbuf; |
| } |
| } |
| } |
| if (mne == NULL && (word & 0x3000) != 0) |
| { |
| static const char *const mnecsr[8] = |
| { |
| NULL, "csrrw", "csrrs", "csrrc", |
| NULL, "csrrwi", "csrrsi", "csrrci" |
| }; |
| static const struct known_csrs known[] = |
| { |
| // This list must remain sorted by NR. |
| { 0x000, "ustatus" }, |
| { 0x001, "fflags" }, |
| { 0x002, "fram" }, |
| { 0x003, "fcsr" }, |
| { 0x004, "uie" }, |
| { 0x005, "utvec" }, |
| { 0x040, "uscratch" }, |
| { 0x041, "uepc" }, |
| { 0x042, "ucause" }, |
| { 0x043, "utval" }, |
| { 0x044, "uip" }, |
| { 0x100, "sstatus" }, |
| { 0x102, "sedeleg" }, |
| { 0x103, "sideleg" }, |
| { 0x104, "sie" }, |
| { 0x105, "stvec" }, |
| { 0x106, "scounteren" }, |
| { 0x140, "sscratch" }, |
| { 0x141, "sepc" }, |
| { 0x142, "scause" }, |
| { 0x143, "stval" }, |
| { 0x144, "sip" }, |
| { 0x180, "satp" }, |
| { 0x200, "vsstatus" }, |
| { 0x204, "vsie" }, |
| { 0x205, "vstvec" }, |
| { 0x240, "vsscratch" }, |
| { 0x241, "vsepc" }, |
| { 0x242, "vscause" }, |
| { 0x243, "vstval" }, |
| { 0x244, "vsip" }, |
| { 0x280, "vsatp" }, |
| { 0x600, "hstatus" }, |
| { 0x602, "hedeleg" }, |
| { 0x603, "hideleg" }, |
| { 0x605, "htimedelta" }, |
| { 0x606, "hcounteren" }, |
| { 0x615, "htimedeltah" }, |
| { 0x680, "hgatp" }, |
| { 0xc00, "cycle" }, |
| { 0xc01, "time" }, |
| { 0xc02, "instret" }, |
| { 0xc03, "hpmcounter3" }, |
| { 0xc04, "hpmcounter4" }, |
| { 0xc05, "hpmcounter5" }, |
| { 0xc06, "hpmcounter6" }, |
| { 0xc07, "hpmcounter7" }, |
| { 0xc08, "hpmcounter8" }, |
| { 0xc09, "hpmcounter9" }, |
| { 0xc0a, "hpmcounter10" }, |
| { 0xc0b, "hpmcounter11" }, |
| { 0xc0c, "hpmcounter12" }, |
| { 0xc0d, "hpmcounter13" }, |
| { 0xc0e, "hpmcounter14" }, |
| { 0xc0f, "hpmcounter15" }, |
| { 0xc10, "hpmcounter16" }, |
| { 0xc11, "hpmcounter17" }, |
| { 0xc12, "hpmcounter18" }, |
| { 0xc13, "hpmcounter19" }, |
| { 0xc14, "hpmcounter20" }, |
| { 0xc15, "hpmcounter21" }, |
| { 0xc16, "hpmcounter22" }, |
| { 0xc17, "hpmcounter23" }, |
| { 0xc18, "hpmcounter24" }, |
| { 0xc19, "hpmcounter25" }, |
| { 0xc1a, "hpmcounter26" }, |
| { 0xc1b, "hpmcounter27" }, |
| { 0xc1c, "hpmcounter28" }, |
| { 0xc1d, "hpmcounter29" }, |
| { 0xc1e, "hpmcounter30" }, |
| { 0xc1f, "hpmcounter31" }, |
| { 0xc80, "cycleh" }, |
| { 0xc81, "timeh" }, |
| { 0xc82, "instreth" }, |
| { 0xc83, "hpmcounter3h" }, |
| { 0xc84, "hpmcounter4h" }, |
| { 0xc85, "hpmcounter5h" }, |
| { 0xc86, "hpmcounter6h" }, |
| { 0xc87, "hpmcounter7h" }, |
| { 0xc88, "hpmcounter8h" }, |
| { 0xc89, "hpmcounter9h" }, |
| { 0xc8a, "hpmcounter10h" }, |
| { 0xc8b, "hpmcounter11h" }, |
| { 0xc8c, "hpmcounter12h" }, |
| { 0xc8d, "hpmcounter13h" }, |
| { 0xc8e, "hpmcounter14h" }, |
| { 0xc8f, "hpmcounter15h" }, |
| { 0xc90, "hpmcounter16h" }, |
| { 0xc91, "hpmcounter17h" }, |
| { 0xc92, "hpmcounter18h" }, |
| { 0xc93, "hpmcounter19h" }, |
| { 0xc94, "hpmcounter20h" }, |
| { 0xc95, "hpmcounter21h" }, |
| { 0xc96, "hpmcounter22h" }, |
| { 0xc97, "hpmcounter23h" }, |
| { 0xc98, "hpmcounter24h" }, |
| { 0xc99, "hpmcounter25h" }, |
| { 0xc9a, "hpmcounter26h" }, |
| { 0xc9b, "hpmcounter27h" }, |
| { 0xc9c, "hpmcounter28h" }, |
| { 0xc9d, "hpmcounter29h" }, |
| { 0xc9e, "hpmcounter30h" }, |
| { 0xc9f, "hpmcounter31h" }, |
| }; |
| uint32_t csr = word >> 20; |
| uint32_t instr = (word >> 12) & 0x7; |
| size_t last = 0; |
| if (rd != 0) |
| op[last++] = REG (rd); |
| struct known_csrs key = { csr, NULL }; |
| struct known_csrs *found = bsearch (&key, known, |
| sizeof (known) / sizeof (known[0]), |
| sizeof (known[0]), |
| compare_csr); |
| if (found) |
| op[last] = (char *) found->name; |
| else |
| { |
| snprintf (addrbuf, sizeof (addrbuf), "0x%" PRIx32, csr); |
| op[last] = addrbuf; |
| } |
| ++last; |
| if ((word & 0x4000) == 0) |
| op[last] = REG ((word >> 15) & 0x1f); |
| else |
| { |
| snprintf (immbuf, sizeof (immbuf), "%" PRIu32, (word >> 15) & UINT32_C(0x1f)); |
| op[last] = immbuf; |
| } |
| if (instr == 1 && rd == 0) |
| mne = "csrw"; |
| else if (instr == 2 && rd == 0) |
| mne = "csrs"; |
| else if (instr == 6 && rd == 0) |
| mne = "csrsi"; |
| else if (instr == 2 && rs1 == 0) |
| mne = "csrr"; |
| else if (instr == 3 && rd == 0) |
| mne = "csrc"; |
| else |
| mne = (char *) mnecsr[instr]; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (strp == NULL && mne == NULL) |
| { |
| len = snprintf (addrbuf, sizeof (addrbuf), "0x%08" PRIx32, word); |
| strp = addrbuf; |
| } |
| } |
| else |
| { |
| // No instruction encodings defined for these sizes yet. |
| char *cp = stpcpy (mnebuf, "0x"); |
| assert (length % 2 == 0); |
| for (size_t i = 0; i < length; i += 2) |
| cp += snprintf (cp, mnebuf + sizeof (mnebuf) - cp, "%04" PRIx16, |
| read_2ubyte_unaligned (data + i)); |
| strp = mnebuf; |
| len = cp - mnebuf; |
| } |
| |
| if (strp == NULL) |
| { |
| |
| if (0) |
| { |
| /* Resize the buffer. */ |
| char *oldbuf; |
| enomem: |
| oldbuf = buf; |
| if (buf == initbuf) |
| buf = malloc (2 * bufsize); |
| else |
| buf = realloc (buf, 2 * bufsize); |
| if (buf == NULL) |
| { |
| buf = oldbuf; |
| retval = ENOMEM; |
| goto do_ret; |
| } |
| bufsize *= 2; |
| |
| bufcnt = 0; |
| } |
| |
| unsigned long string_end_idx = 0; |
| fmt = save_fmt; |
| const char *deferred_start = NULL; |
| size_t deferred_len = 0; |
| // XXX Can we get this from color.c? |
| static const char color_off[] = "\e[0m"; |
| while (*fmt != '\0') |
| { |
| if (*fmt != '%') |
| { |
| char ch = *fmt++; |
| if (ch == '\\') |
| { |
| switch ((ch = *fmt++)) |
| { |
| case '0' ... '7': |
| { |
| int val = ch - '0'; |
| ch = *fmt; |
| if (ch >= '0' && ch <= '7') |
| { |
| val *= 8; |
| val += ch - '0'; |
| ch = *++fmt; |
| if (ch >= '0' && ch <= '7' && val < 32) |
| { |
| val *= 8; |
| val += ch - '0'; |
| ++fmt; |
| } |
| } |
| ch = val; |
| } |
| break; |
| |
| case 'n': |
| ch = '\n'; |
| break; |
| |
| case 't': |
| ch = '\t'; |
| break; |
| |
| default: |
| retval = EINVAL; |
| goto do_ret; |
| } |
| } |
| else if (ch == '\e' && *fmt == '[') |
| { |
| deferred_start = fmt - 1; |
| do |
| ++fmt; |
| while (*fmt != 'm' && *fmt != '\0'); |
| |
| if (*fmt == 'm') |
| { |
| deferred_len = ++fmt - deferred_start; |
| continue; |
| } |
| |
| fmt = deferred_start + 1; |
| deferred_start = NULL; |
| } |
| ADD_CHAR (ch); |
| continue; |
| } |
| ++fmt; |
| |
| int width = 0; |
| while (isdigit (*fmt)) |
| width = width * 10 + (*fmt++ - '0'); |
| |
| int prec = 0; |
| if (*fmt == '.') |
| while (isdigit (*++fmt)) |
| prec = prec * 10 + (*fmt - '0'); |
| |
| size_t start_idx = bufcnt; |
| size_t non_printing = 0; |
| switch (*fmt++) |
| { |
| case 'm': |
| if (deferred_start != NULL) |
| { |
| ADD_NSTRING (deferred_start, deferred_len); |
| non_printing += deferred_len; |
| } |
| |
| ADD_STRING (mne); |
| |
| if (deferred_start != NULL) |
| { |
| ADD_STRING (color_off); |
| non_printing += strlen (color_off); |
| } |
| |
| string_end_idx = bufcnt; |
| break; |
| |
| case 'o': |
| if (op[prec - 1] != NULL) |
| { |
| if (deferred_start != NULL) |
| { |
| ADD_NSTRING (deferred_start, deferred_len); |
| non_printing += deferred_len; |
| } |
| |
| ADD_STRING (op[prec - 1]); |
| |
| if (deferred_start != NULL) |
| { |
| ADD_STRING (color_off); |
| non_printing += strlen (color_off); |
| } |
| |
| string_end_idx = bufcnt; |
| } |
| else |
| bufcnt = string_end_idx; |
| break; |
| |
| case 'e': |
| string_end_idx = bufcnt; |
| break; |
| |
| case 'a': |
| /* Pad to requested column. */ |
| while (bufcnt - non_printing < (size_t) width) |
| ADD_CHAR (' '); |
| width = 0; |
| break; |
| |
| case 'l': |
| // TODO |
| break; |
| |
| default: |
| abort(); |
| } |
| |
| /* Pad according to the specified width. */ |
| while (bufcnt - non_printing < start_idx + width) |
| ADD_CHAR (' '); |
| } |
| |
| strp = buf; |
| len = bufcnt; |
| } |
| |
| addr += length; |
| *startp = data + length; |
| retval = outcb (strp, len, outcbarg); |
| if (retval != 0) |
| break; |
| } |
| |
| do_ret: |
| if (buf != initbuf) |
| free (buf); |
| |
| return retval; |
| } |