hppa: use capstone disassembler

This commit is contained in:
Anton Kochkov 2024-05-04 18:48:21 +08:00 committed by NOT XVilka
parent ac6276d220
commit 6cacb99e98
14 changed files with 1070 additions and 21 deletions

View file

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2024-2025 Anton Kochkov <anton.kochkov@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_types.h>
#include <capstone.h>
#ifndef RZ_HPPA_H
#define RZ_HPPA_H
typedef struct {
csh h;
cs_mode mode;
cs_insn *insn;
ut32 count;
ut32 word;
RzPVector /*<RzAsmTokenPattern *>*/ *token_patterns;
} RzAsmHPPAContext;
#endif // RZ_HPPA_H

View file

@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2023 billow <billow.fun@gmail.com>
// SPDX-FileCopyrightText: 2024-2025 Anton Kochkov <anton.kochkov@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <capstone/capstone.h>
#include <capstone/hppa.h>
#include "hppa.h"
static inline cs_mode hppa_cpu_to_cs_mode(const char *cpu_type) {
if (RZ_STR_ISEMPTY(cpu_type)) {
return CS_MODE_HPPA_11;
}
if (!strcmp(cpu_type, "hppa1.1")) {
return CS_MODE_HPPA_11;
}
if (!strcmp(cpu_type, "hppa2.0")) {
return CS_MODE_HPPA_20;
}
if (!strcmp(cpu_type, "hppa2.0w")) {
return CS_MODE_HPPA_20W;
}
return CS_MODE_HPPA_11;
}
static inline bool hppa_setup_cs_handle(RzAsmHPPAContext *ctx, const char *cpu, const char *features, bool big_endian) {
const cs_mode mode = hppa_cpu_to_cs_mode(cpu) | (big_endian ? CS_MODE_BIG_ENDIAN : CS_MODE_LITTLE_ENDIAN);
if (mode != ctx->mode) {
cs_close(&ctx->h);
ctx->h = 0;
ctx->mode = mode;
}
if (ctx->h != 0) {
return true;
}
cs_err err = cs_open(CS_ARCH_HPPA, mode, &ctx->h);
if (err) {
RZ_LOG_ERROR("Failed on cs_open() with error returned: %u\n", err);
return false;
}
err = cs_option(ctx->h, CS_OPT_DETAIL,
RZ_STR_ISNOTEMPTY(features) || features == NULL ? CS_OPT_ON : CS_OPT_OFF);
if (err) {
RZ_LOG_ERROR("Failed on cs_open() with error returned: %u\n", err);
return false;
}
return true;
}
static inline ut8 hppa_op_count(const cs_insn *insn) {
return insn->detail->hppa.op_count;
}
static inline const cs_hppa_op *hppa_op_get(const cs_insn *insn, int idx) {
if (idx >= hppa_op_count(insn)) {
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\"\n",
idx, hppa_op_count(insn), insn->mnemonic, insn->op_str);
rz_warn_if_reached();
return NULL;
}
return &insn->detail->hppa.operands[idx];
}
static inline const char *hppa_op_as_reg(const RzAsmHPPAContext *ctx, int idx) {
const cs_hppa_op *op = hppa_op_get(ctx->insn, idx);
if (op->type != HPPA_OP_REG) {
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [reg]\n",
idx, hppa_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
rz_warn_if_reached();
return NULL;
}
return cs_reg_name(ctx->h, op->reg);
}
static inline const char *hppa_op_as_mem(const RzAsmHPPAContext *ctx, int idx) {
const cs_hppa_op *op = hppa_op_get(ctx->insn, idx);
if (op->type != HPPA_OP_MEM) {
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [reg]\n",
idx, hppa_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
rz_warn_if_reached();
return NULL;
}
return cs_reg_name(ctx->h, op->reg);
}
static inline st64 hppa_op_as_imm(const RzAsmHPPAContext *ctx, int idx) {
const cs_hppa_op *op = hppa_op_get(ctx->insn, idx);
if (op->type != HPPA_OP_IMM) {
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [imm]\n",
idx, hppa_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
rz_warn_if_reached();
return 0;
}
return op->imm;
}
static inline st64 hppa_op_as_disp(const RzAsmHPPAContext *ctx, int idx) {
const cs_hppa_op *op = hppa_op_get(ctx->insn, idx);
if (op->type != HPPA_OP_DISP) {
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [imm]\n",
idx, hppa_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
rz_warn_if_reached();
return 0;
}
return op->imm;
}
static inline st64 hppa_op_as_target(const RzAsmHPPAContext *ctx, int idx) {
const cs_hppa_op *op = hppa_op_get(ctx->insn, idx);
if (op->type != HPPA_OP_TARGET) {
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [imm]\n",
idx, hppa_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
rz_warn_if_reached();
return 0;
}
return op->imm;
}

View file

@ -338,6 +338,7 @@ if capstone_dep.version() == 'next'
# plugins
arch_plugins_list += [
'alpha_cs',
'hppa_cs',
'loongarch_cs',
'xtensa_cs',
]
@ -346,6 +347,7 @@ if capstone_dep.version() == 'next'
arch_plugin_sources += [
'p/arch_alpha.c',
'p/arch_loongarch_cs.c',
'p/arch_hppa_cs.c',
'p/arch_xtensa_cs.c',
]

View file

@ -0,0 +1,504 @@
// SPDX-FileCopyrightText: 2024 Anton Kochkov <anton.kochkov@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_types.h>
#include <rz_lib.h>
#include <rz_asm.h>
#include <capstone/capstone.h>
#include <hppa/hppa.inc>
static char *hppa_reg_profile(RzAnalysis *analysis) {
if (analysis->bits == 64) {
const char *p =
"=PC pc\n"
"=SP r30\n"
"=A0 r26\n"
"=A1 r25\n"
"=A2 r24\n"
"=A3 r23\n"
"=R0 ret0\n"
"gpr r0 .64 0 0\n"
"gpr r1 .64 8 0\n"
"gpr r2 .64 16 0\n"
"gpr r3 .64 24 0\n"
"gpr r4 .64 32 0\n"
"gpr r5 .64 40 0\n"
"gpr r6 .64 48 0\n"
"gpr r7 .64 56 0\n"
"gpr r8 .64 64 0\n"
"gpr r9 .64 72 0\n"
"gpr r10 .64 80 0\n"
"gpr r11 .64 88 0\n"
"gpr r12 .64 96 0\n"
"gpr r13 .64 104 0\n"
"gpr r14 .64 112 0\n"
"gpr r15 .64 120 0\n"
"gpr r16 .64 128 0\n"
"gpr r17 .64 136 0\n"
"gpr r18 .64 144 0\n"
"gpr r19 .64 152 0\n"
"gpr r20 .64 160 0\n"
"gpr r21 .64 168 0\n"
"gpr r22 .64 176 0\n"
"gpr r23 .64 184 0\n"
"gpr r24 .64 192 0\n"
"gpr r25 .64 200 0\n"
"gpr r26 .64 208 0\n"
"gpr r27 .64 216 0\n"
"gpr r28 .64 224 0\n"
"gpr r29 .64 232 0\n"
"gpr r30 .64 240 0\n"
"gpr r31 .64 248 0\n"
"ctr sr0 .64 256 0\n"
"ctr sr1 .64 264 0\n"
"ctr sr2 .64 272 0\n"
"ctr sr3 .64 280 0\n"
"ctr sr4 .64 288 0\n"
"ctr sr5 .64 296 0\n"
"ctr sr6 .64 304 0\n"
"ctr sr7 .64 312 0\n"
"flg psw .64 320 0\n";
return strdup(p);
} else {
const char *p =
"=PC pc\n"
"=SP r30\n"
"=A0 r26\n"
"=A1 r25\n"
"=A2 r24\n"
"=A3 r23\n"
"gpr r0 .32 0 0\n"
"gpr r1 .32 4 0\n"
"gpr r2 .32 8 0\n"
"gpr r3 .32 12 0\n"
"gpr r4 .32 16 0\n"
"gpr r5 .32 20 0\n"
"gpr r6 .32 24 0\n"
"gpr r7 .32 28 0\n"
"gpr r8 .32 32 0\n"
"gpr r9 .32 36 0\n"
"gpr r10 .32 40 0\n"
"gpr r11 .32 44 0\n"
"gpr r12 .32 48 0\n"
"gpr r13 .32 52 0\n"
"gpr r14 .32 56 0\n"
"gpr r15 .32 60 0\n"
"gpr r16 .32 64 0\n"
"gpr r17 .32 68 0\n"
"gpr r18 .32 72 0\n"
"gpr r19 .32 76 0\n"
"gpr r20 .32 80 0\n"
"gpr r21 .32 84 0\n"
"gpr r22 .32 88 0\n"
"gpr r23 .32 92 0\n"
"gpr r24 .32 96 0\n"
"gpr r25 .32 100 0\n"
"gpr r26 .32 104 0\n"
"gpr r27 .32 108 0\n"
"gpr r28 .32 112 0\n"
"gpr r29 .32 116 0\n"
"gpr r30 .32 120 0\n"
"gpr r31 .32 124 0\n"
"ctr sr0 .32 128 0\n"
"ctr sr1 .32 132 0\n"
"ctr sr2 .32 136 0\n"
"ctr sr3 .32 140 0\n"
"ctr sr4 .32 144 0\n"
"ctr sr5 .32 148 0\n"
"ctr sr6 .32 152 0\n"
"ctr sr7 .32 156 0\n"
"flg psw .32 160 0\n";
return strdup(p);
}
}
static inline void hppa_fillval(const RzReg *rz_reg, csh handle, RzAnalysisValue *av, cs_hppa_op *hop) {
switch (hop->type) {
case HPPA_OP_INVALID:
default:
av->type = RZ_ANALYSIS_VAL_UNK;
break;
case HPPA_OP_IMM:
case HPPA_OP_DISP:
case HPPA_OP_TARGET:
av->type = RZ_ANALYSIS_VAL_IMM;
av->imm = hop->imm;
break;
case HPPA_OP_REG:
case HPPA_OP_IDX_REG:
av->type = RZ_ANALYSIS_VAL_REG;
av->reg = rz_reg_get(rz_reg, cs_reg_name(handle, hop->reg), RZ_REG_TYPE_ANY);
break;
case HPPA_OP_MEM:
av->type = RZ_ANALYSIS_VAL_MEM;
// FIXME: Handle also the space
av->reg = rz_reg_get(rz_reg, cs_reg_name(handle, hop->mem.base), RZ_REG_TYPE_ANY);
av->delta = 0;
break;
}
}
static void hppa_fillvals(const RzAsmHPPAContext *ctx, RzAnalysis *a, RzAnalysisOp *op) {
uint8_t srci = 0;
cs_hppa *hc = &ctx->insn->detail->hppa;
for (uint8_t i = 0; i < hc->op_count; ++i) {
cs_hppa_op *hop = &hc->operands[i];
RzAnalysisValue *av = rz_analysis_value_new();
hppa_fillval(a->reg, ctx->h, av, hop);
if (hop->access & CS_AC_READ) {
av->access |= RZ_ANALYSIS_ACC_R;
op->src[srci++] = av;
}
if (hop->access & CS_AC_WRITE) {
av->access |= RZ_ANALYSIS_ACC_W;
if (srci > 0 && av == op->src[srci - 1]) {
av = rz_mem_dup(av, sizeof(RzAnalysisValue));
}
op->dst = av;
}
}
}
static RzStructuredData *hppa_opex(csh handle, cs_insn *insn) {
if (!insn->detail) {
return NULL;
}
RzStructuredData *root = rz_structured_data_new_map();
if (!root) {
return NULL;
}
RzStructuredData *opex = rz_structured_data_map_add_map(root, "opex");
if (!opex) {
rz_structured_data_free(root);
return NULL;
}
RzStructuredData *operands = rz_structured_data_map_add_array(opex, "operands");
cs_hppa *hpc = &insn->detail->hppa;
for (st32 i = 0; i < hpc->op_count; i++) {
cs_hppa_op *op = hpc->operands + i;
RzStructuredData *operand = rz_structured_data_array_add_map(operands);
switch (op->type) {
case HPPA_OP_REG: {
rz_structured_data_map_add_string(operand, "type", "reg");
rz_structured_data_map_add_string(operand, "value", cs_reg_name(handle, op->reg));
break;
}
case HPPA_OP_IDX_REG: {
rz_structured_data_map_add_string(operand, "type", "idx_reg");
rz_structured_data_map_add_string(operand, "value", cs_reg_name(handle, op->reg));
break;
}
case HPPA_OP_IMM: {
rz_structured_data_map_add_string(operand, "type", "imm");
rz_structured_data_map_add_signed(operand, "value", op->imm);
break;
}
case HPPA_OP_DISP: {
rz_structured_data_map_add_string(operand, "type", "disp");
rz_structured_data_map_add_signed(operand, "value", op->imm);
break;
}
case HPPA_OP_TARGET: {
rz_structured_data_map_add_string(operand, "type", "target");
rz_structured_data_map_add_signed(operand, "value", op->imm);
break;
}
case HPPA_OP_MEM: {
rz_structured_data_map_add_string(operand, "type", "mem");
if (op->mem.base != HPPA_REG_INVALID) {
rz_structured_data_map_add_string(operand, "base", cs_reg_name(handle, op->mem.base));
}
if (op->mem.space != HPPA_REG_INVALID) {
rz_structured_data_map_add_string(operand, "space", cs_reg_name(handle, op->mem.space));
} else {
rz_structured_data_map_add_string(operand, "space", "unavailable");
}
break;
}
default:
rz_structured_data_map_add_string(operand, "type", "invalid");
break;
}
}
return root;
}
static void hppa_op_set_type(const RzAsmHPPAContext *ctx, RzAnalysisOp *op) {
switch (ctx->insn->id) {
default:
op->type = RZ_ANALYSIS_OP_TYPE_UNK;
break;
case HPPA_INS_NOP:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
case HPPA_INS_ADD:
case HPPA_INS_ADDI:
case HPPA_INS_ADDIO:
case HPPA_INS_ADDIT:
case HPPA_INS_ADDITO:
case HPPA_INS_ADDIL:
case HPPA_INS_ADDC:
case HPPA_INS_ADDCO:
case HPPA_INS_ADDL:
case HPPA_INS_ADDO:
op->type = RZ_ANALYSIS_OP_TYPE_ADD;
break;
case HPPA_INS_ADDB:
case HPPA_INS_ADDIB:
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 2);
break;
case HPPA_INS_ADDBT:
case HPPA_INS_ADDBF:
case HPPA_INS_ADDIBT:
case HPPA_INS_ADDIBF:
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 2);
op->fail = ctx->insn->address + ctx->insn->size;
break;
case HPPA_INS_AND:
case HPPA_INS_ANDCM:
op->type = RZ_ANALYSIS_OP_TYPE_AND;
break;
case HPPA_INS_BB:
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 2);
op->fail = ctx->insn->address + ctx->insn->size;
break;
case HPPA_INS_BE:
op->type = RZ_ANALYSIS_OP_TYPE_IRCALL;
op->reg = hppa_op_as_mem(ctx, 1);
break;
case HPPA_INS_BL:
op->type = RZ_ANALYSIS_OP_TYPE_IRCALL;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 0);
break;
case HPPA_INS_BLE:
op->type = RZ_ANALYSIS_OP_TYPE_IRCALL;
op->reg = hppa_op_as_mem(ctx, 1);
break;
case HPPA_INS_BLR:
op->type = RZ_ANALYSIS_OP_TYPE_IRJMP;
op->reg = hppa_op_as_reg(ctx, 0);
break;
case HPPA_INS_BV:
op->type = RZ_ANALYSIS_OP_TYPE_IRCALL;
// FIXME: Should be result of the *two* registers actually
op->reg = hppa_op_as_mem(ctx, 1);
break;
case HPPA_INS_BVB:
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 1);
break;
case HPPA_INS_GATE:
op->type = RZ_ANALYSIS_OP_TYPE_JMP;
op->jump = hppa_op_as_target(ctx, 0);
break;
case HPPA_INS_LDB:
case HPPA_INS_LDBS:
case HPPA_INS_LDCD:
case HPPA_INS_LDCW:
case HPPA_INS_LDCWS:
case HPPA_INS_LDD:
case HPPA_INS_LDDA:
case HPPA_INS_LDH:
case HPPA_INS_LDHS:
case HPPA_INS_LDI:
case HPPA_INS_LDW:
case HPPA_INS_LDWA:
case HPPA_INS_LDWAS:
case HPPA_INS_LDWM:
case HPPA_INS_LDWS:
op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
const cs_hppa_op *op1 = hppa_op_get(ctx->insn, 1);
if (op1->type == HPPA_OP_REG && op1->reg == HPPA_REG_GR30 /* SP */) {
op->stackop = RZ_ANALYSIS_STACK_GET;
op->stackptr = 0;
}
if (op1->type == HPPA_OP_IMM) {
op->ptr = (st64)hppa_op_as_disp(ctx, 0);
}
break;
case HPPA_INS_LDSID:
case HPPA_INS_LDHX:
case HPPA_INS_LDBX:
case HPPA_INS_LDCWX:
case HPPA_INS_LDWAX:
case HPPA_INS_LDWX:
op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
break;
case HPPA_INS_LDIL:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case HPPA_INS_LCI:
case HPPA_INS_LDO:
op->type = RZ_ANALYSIS_OP_TYPE_LEA;
break;
case HPPA_INS_MOVB:
case HPPA_INS_MOVIB:
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 2);
op->fail = ctx->insn->address + ctx->insn->size;
break;
case HPPA_INS_OR:
op->type = RZ_ANALYSIS_OP_TYPE_OR;
break;
case HPPA_INS_CALL:
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = hppa_op_as_target(ctx, 0);
break;
case HPPA_INS_COMIB:
case HPPA_INS_COMIBT:
case HPPA_INS_COMIBF:
case HPPA_INS_COMB:
case HPPA_INS_COMBT:
case HPPA_INS_COMBF:
op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
op->jump = ctx->insn->address + hppa_op_as_target(ctx, 2);
op->fail = ctx->insn->address + ctx->insn->size;
break;
case HPPA_INS_STB:
case HPPA_INS_STBS:
case HPPA_INS_STBY:
case HPPA_INS_STBYS:
case HPPA_INS_STD:
case HPPA_INS_STDA:
case HPPA_INS_STDBY:
case HPPA_INS_STH:
case HPPA_INS_STHS:
case HPPA_INS_STW:
case HPPA_INS_STWA:
case HPPA_INS_STWAS:
case HPPA_INS_STWS:
case HPPA_INS_STWM:
op->type = RZ_ANALYSIS_OP_TYPE_STORE;
const cs_hppa_op *op2 = hppa_op_get(ctx->insn, 2);
if (op2->type == HPPA_OP_REG && op2->reg == HPPA_REG_GR30 /* SP */) {
op->stackop = RZ_ANALYSIS_STACK_SET;
op->stackptr = 0;
}
if (op2->type == HPPA_OP_IMM) {
op->ptr = (st64)hppa_op_as_disp(ctx, 1);
}
break;
case HPPA_INS_SUB:
case HPPA_INS_SUBB:
case HPPA_INS_SUBBO:
case HPPA_INS_SUBI:
case HPPA_INS_SUBIO:
case HPPA_INS_SUBO:
case HPPA_INS_SUBT:
case HPPA_INS_SUBTO:
op->type = RZ_ANALYSIS_OP_TYPE_SUB;
break;
case HPPA_INS_XOR:
op->type = RZ_ANALYSIS_OP_TYPE_XOR;
break;
}
}
static int
hppa_op(RzAnalysis *a, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len, RzAnalysisOpMask mask) {
if (!(a && op && data && len > 0)) {
return -1;
}
RzAsmHPPAContext *ctx = a->plugin_data;
if (!hppa_setup_cs_handle(ctx, a->cpu, NULL, a->big_endian)) {
return -1;
}
op->size = 4;
ctx->insn = NULL;
ctx->count = cs_disasm(ctx->h, (const ut8 *)data, len, addr, 1, &ctx->insn);
if (ctx->count <= 0 || !ctx->insn) {
op->type = RZ_ANALYSIS_OP_TYPE_ILL;
if (mask & RZ_ANALYSIS_OP_MASK_DISASM) {
op->mnemonic = strdup("invalid");
}
goto beach;
}
if (mask & RZ_ANALYSIS_OP_MASK_DISASM) {
op->mnemonic = rz_str_newf("%s%s%s",
ctx->insn->mnemonic, ctx->insn->op_str[0] ? " " : "", ctx->insn->op_str);
}
op->size = ctx->insn->size;
op->id = (int)ctx->insn->id;
op->addr = ctx->insn->address;
hppa_op_set_type(ctx, op);
if (mask & RZ_ANALYSIS_OP_MASK_OPEX) {
op->opex = hppa_opex(ctx->h, ctx->insn);
}
if (mask & RZ_ANALYSIS_OP_MASK_VAL) {
hppa_fillvals(ctx, a, op);
}
beach:
cs_free(ctx->insn, ctx->count);
return op->size;
}
static int hppa_archinfo(RzAnalysis *a, RzAnalysisInfoType query) {
switch (query) {
case RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE:
return 4;
case RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE:
return 4;
case RZ_ANALYSIS_ARCHINFO_TEXT_ALIGN:
case RZ_ANALYSIS_ARCHINFO_DATA_ALIGN:
case RZ_ANALYSIS_ARCHINFO_CAN_USE_POINTERS:
default:
return -1;
}
}
static bool hppa_init(void **u) {
if (!u) {
return false;
}
RzAsmHPPAContext *ctx = RZ_NEW0(RzAsmHPPAContext);
if (!ctx) {
return false;
}
*u = ctx;
return true;
}
static bool hppa_fini(void *u) {
if (!u) {
return true;
}
RzAsmHPPAContext *ctx = u;
cs_close(&ctx->h);
free(u);
return true;
}
RzAnalysisPlugin rz_analysis_plugin_hppa_cs = {
.name = "hppa",
.desc = "Capstone HP PA-RISC analysis plugin",
.author = "xvilka",
.license = "LGPL3",
.arch = "hppa",
.bits = 32 | 64,
.get_reg_profile = hppa_reg_profile,
.archinfo = hppa_archinfo,
.op = hppa_op,
.init = hppa_init,
.fini = hppa_fini,
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_ANALYSIS,
.data = &rz_analysis_plugin_hppa_cs,
.version = RZ_VERSION
};
#endif

View file

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 RizinOrg <info@rizin.re>
// SPDX-License-Identifier: LGPL-3.0-only
#include <deprecated_arch_helper.h>
#include "analysis/analysis_hppa_cs.c"
#include "asm/asm_hppa_cs.c"
RZ_ARCH_PLUGIN_DEFINE_DEPRECATED(hppa_cs);

View file

@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: 2024 Anton Kochkov <anton.kochkov@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_asm.h>
#include <rz_lib.h>
#include <capstone/capstone.h>
static int disassemble(RzAsm *a, RzAsmOp *op, const ut8 *buf, int len) {
if (!buf || !op || !a->plugin_data) {
return -1;
}
RzAsmHPPAContext *ctx = a->plugin_data;
if (!hppa_setup_cs_handle(ctx, a->cpu, a->features, a->big_endian)) {
return -1;
}
op->size = 4;
ctx->insn = NULL;
ctx->count = cs_disasm(ctx->h, buf, len, a->pc, 1, &ctx->insn);
if (ctx->count <= 0) {
RZ_LOG_ERROR("HPPA: disasm error @ 0x%08" PFMT64x ", len = %d\n", a->pc, len);
rz_asm_op_set_asm(op, "invalid");
goto beach;
}
op->size = ctx->insn->size;
rz_asm_op_setf_asm(op, "%s%s%s",
ctx->insn->mnemonic, RZ_STR_ISNOTEMPTY(ctx->insn->op_str) ? " " : "", ctx->insn->op_str);
op->asm_toks = rz_asm_tokenize_asm_regex(&op->buf_asm, ctx->token_patterns);
beach:
cs_free(ctx->insn, ctx->count);
ctx->insn = NULL;
ctx->count = 0;
return op->size;
}
#define TOKEN(_type, _pat) \
do { \
RzAsmTokenPattern *pat = RZ_NEW0(RzAsmTokenPattern); \
pat->type = RZ_ASM_TOKEN_##_type; \
pat->pattern = strdup(_pat); \
rz_pvector_push(pvec, pat); \
} while (0)
static RZ_OWN RzPVector /*<RzAsmTokenPattern *>*/ *get_token_patterns() {
RzPVector *pvec = rz_pvector_new(rz_asm_token_pattern_free);
if (!pvec) {
return NULL;
}
TOKEN(META, "(\\[|\\]|-)");
TOKEN(META, "(\\+[rc]?)");
TOKEN(NUMBER, "(0x[[:digit:]abcdef]+)");
TOKEN(REGISTER, "([cfstr]{1,2}[[:digit:]]{1,2})|(sp|psw|pc|rp|dp|ret0|ret1|rctr|pidr1|pidr2|pidr3|ccr|sar|iva|eiem|itmr|pcsq|pcoq|iir|isr|ior|ipsw|eirr|flags)");
TOKEN(MNEMONIC, "([[:alpha:]]+[[:alnum:]\\,]*([[:alpha:]]+|(<|>|=|\\*| )+))");
TOKEN(SEPARATOR, "([[:blank:]]+)|([,;\\(\\)\\{\\}:])");
TOKEN(NUMBER, "([[:digit:]]+)");
return pvec;
}
static bool init(void **u) {
if (!u) {
return false;
}
// u = RzAsm.plugin_data
RzAsmHPPAContext *ctx = NULL;
if (*u) {
rz_mem_memzero(*u, sizeof(RzAsmHPPAContext));
ctx = *u;
} else {
ctx = RZ_NEW0(RzAsmHPPAContext);
if (!ctx) {
return false;
}
*u = ctx;
}
ctx->token_patterns = get_token_patterns();
rz_asm_compile_token_patterns(ctx->token_patterns);
return true;
}
static bool fini(void *u) {
if (!u) {
return true;
}
RzAsmHPPAContext *ctx = u;
cs_close(&ctx->h);
rz_pvector_free(ctx->token_patterns);
free(u);
return true;
}
RzAsmPlugin rz_asm_plugin_hppa_cs = {
.name = "hppa",
.arch = "hppa",
.author = "xvilka",
.license = "LGPL3",
.bits = 32 | 64,
.endian = RZ_SYS_ENDIAN_LITTLE | RZ_SYS_ENDIAN_BIG,
.cpus = "hppa1.1,hppa2.0,hppa2.0w",
.desc = "HP PA-RISC Capstone-based disassembler",
.disassemble = &disassemble,
.init = &init,
.fini = &fini,
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_ASM,
.data = &rz_asm_plugin_hppa_cs,
.version = RZ_VERSION
};
#endif

View file

@ -15,6 +15,7 @@
#define SPARC_OFFSET_PLT_ENTRY_FROM_GOT_ADDR -0x6
#define X86_OFFSET_PLT_ENTRY_FROM_GOT_ADDR -0x6
#define X86_PLT_ENTRY_SIZE 0x10
#define PARISC_PLT_ENTRY_SIZE 0x10
#define UNHANDL_IMPORT(NAME, NUM) \
RZ_LOG_WARN(NAME ": Unhandled ELF relocation for import %d\n", NUM); \
@ -64,6 +65,35 @@ static ut64 get_import_addr_mips(ELFOBJ *bin, RzBinElfReloc *rel) {
return plt_addr;
}
static ut64 get_import_addr_parisc(ELFOBJ *bin, RzBinElfReloc *rel) {
// Get PLT section
RzBinElfSection *plt_sec = Elf_(rz_bin_elf_get_section_with_name)(bin, ".plt");
if (!plt_sec) {
return UT64_MAX;
}
ut64 plt_addr = plt_sec->rva;
ut64 got_addr = 0;
// Get GOT section
RzBinElfSection *got_sec = Elf_(rz_bin_elf_get_section_with_name)(bin, ".got");
if (got_sec) {
got_addr = got_sec->rva;
}
switch (rel->type) {
case R_PARISC_IPLT:
// IPLT uses direct PLT entries
return plt_addr + (rel->sym + 1) * PARISC_PLT_ENTRY_SIZE;
default:
// For other relocation types, try GOT first, then PLT
if (got_sec) {
const ut64 got_entry_size = 8; // 64-bit pointers
return got_addr + rel->sym * got_entry_size;
}
return plt_addr + (rel->sym + 1) * PARISC_PLT_ENTRY_SIZE;
}
}
/**
* \brief Determines and returns the import address for the given relocation
* for the Hexagon architecture.
@ -350,6 +380,8 @@ static ut64 get_import_addr_aux(ELFOBJ *bin, RzBinElfReloc *reloc) {
case EM_MIPS_X:
case EM_IMG1:
return get_import_addr_mips(bin, reloc);
case EM_PARISC:
return get_import_addr_parisc(bin, reloc);
case EM_RISCV:
return get_import_addr_riscv(bin, reloc);
case EM_SPARC:

View file

@ -29,7 +29,7 @@ struct class_translation {
const char *name;
};
struct cpu_mips_translation {
struct cpu_arch_translation {
Elf_(Word) arch;
const char *name;
};
@ -233,7 +233,7 @@ static const struct class_translation class_translation_table[] = {
{ ELFCLASS64, "ELF64" }
};
static const struct cpu_mips_translation gnu_mips_mach_translation_table[] = {
static const struct cpu_arch_translation gnu_mips_mach_translation_table[] = {
{ EF_MIPS_MACH_3900, "3900 " },
{ EF_MIPS_MACH_4010, "4010 " },
{ EF_MIPS_MACH_4100, "4100 " },
@ -272,7 +272,7 @@ static const struct mips_bits_translation mips_bits_translation_table[] = {
{ EF_MIPS_ARCH_64R6, 64 },
};
static const struct cpu_mips_translation gnu_mips_arch_translation_table32[] = {
static const struct cpu_arch_translation gnu_mips_arch_translation_table32[] = {
{ EF_MIPS_ARCH_1, "mips32" },
{ EF_MIPS_ARCH_2, "mips2" },
{ EF_MIPS_ARCH_3, "mips3" },
@ -286,7 +286,7 @@ static const struct cpu_mips_translation gnu_mips_arch_translation_table32[] = {
{ EF_MIPS_ARCH_64R6, "mips64r6" },
};
static const struct cpu_mips_translation gnu_mips_arch_translation_table64[] = {
static const struct cpu_arch_translation gnu_mips_arch_translation_table64[] = {
{ EF_MIPS_ARCH_1, "mips64" }, // also used for generic mips, so we default to mips64
{ EF_MIPS_ARCH_2, "mips64r2" },
{ EF_MIPS_ARCH_3, "mips64r3" },
@ -300,6 +300,13 @@ static const struct cpu_mips_translation gnu_mips_arch_translation_table64[] = {
{ EF_MIPS_ARCH_64R6, "mips64r6" },
};
static const struct cpu_arch_translation cpu_hppa_translation_table[] = {
{ EFA_PARISC_1_0, "hppa1.0" },
{ EFA_PARISC_1_1, "hppa2.0" }, // In practice many ELF file set as 1.1 version
// contain 2.0 opcodes
{ EFA_PARISC_2_0, "hppa2.0" },
};
static const struct arch_translation arch_translation_table[] = {
{ EM_ALPHA, "alpha" },
{ EM_ARC, "arc" },
@ -754,6 +761,10 @@ static bool arch_is_arcompact(ELFOBJ *bin) {
bin->ehdr.e_machine == EM_ARC_COMPACT3;
}
static bool arch_is_parisc(ELFOBJ *bin) {
return bin->ehdr.e_machine == EM_PARISC;
}
static char *read_elf_intrp(ELFOBJ *bin, ut64 addr, size_t size) {
char *str = malloc(size + 1);
if (!str) {
@ -1060,6 +1071,18 @@ static char *get_abi_mips(ELFOBJ *bin) {
return rz_strbuf_drain_nofree(&sb);
}
static char *get_cpu_hppa(ELFOBJ *bin) {
Elf_(Word) hppa_arch = bin->ehdr.e_flags & EF_PARISC_ARCH;
for (size_t i = 0; i < RZ_ARRAY_SIZE(cpu_hppa_translation_table); i++) {
if (hppa_arch == cpu_hppa_translation_table[i].arch) {
return strdup(cpu_hppa_translation_table[i].name);
}
}
return strdup(" Unknown HP PARISC ISA");
}
/**
* \brief List all imported lib
* \param elf binary
@ -1647,6 +1670,8 @@ RZ_OWN char *Elf_(rz_bin_elf_get_cpu)(RZ_NONNULL ELFOBJ *bin) {
// Capstone has only v8 and v9 for now.
// So no finer distinction necessary.
return bin->ehdr.e_machine == EM_SPARC ? strdup("v8") : strdup("v9");
} else if (arch_is_parisc(bin)) {
return get_cpu_hppa(bin);
}
return NULL;

View file

@ -1082,6 +1082,22 @@ static RzBinReloc *reloc_convert_alpha(ELFOBJ *bin, RzBinElfReloc *rel, ut64 GOT
}
}
static RzBinReloc *reloc_convert_parisc(ELFOBJ *bin, RzBinElfReloc *rel, ut64 GOT) {
ut64 P = rel->vaddr;
switch (rel->type) {
case R_PARISC_NONE:
return reloc_convert_set(bin, rel, 0, "R_PARISC_NONE");
case R_PARISC_DIR32: ADD(32, 0, "R_PARISC_DIR32", RZ_RELOC_BASE_UNKNOWN);
case R_PARISC_DIR64: ADD(64, 0, "R_PARISC_DIR64", RZ_RELOC_BASE_UNKNOWN);
case R_PARISC_COPY: ADD(64, 0, "R_PARISC_COPY", RZ_RELOC_BASE_UNKNOWN); // copy symbol at runtime
case R_PARISC_IPLT: ADD(64, -P, "R_PARISC_IPLT", RZ_RELOC_BASE_PLT_SYMBOL);
case R_PARISC_EPLT: ADD(64, -P, "R_PARISC_EPLT", RZ_RELOC_BASE_PLT_SYMBOL);
// FIXME: Quite a few relocatons missing here.
default: UNSUPP("PARISC");
}
}
static RzBinReloc *reloc_convert_hexagon(ELFOBJ *bin, RzBinElfReloc *rel, ut64 GOT) {
ut64 P = rel->vaddr;
@ -1430,7 +1446,8 @@ RZ_OWN RzBinReloc *Elf_(rz_bin_elf_convert_relocation)(RZ_NONNULL ELFOBJ *bin, R
case EM_IAMCU: ARCH_MISSING("EM_IAMCU");
case EM_860: ARCH_MISSING("EM_860");
case EM_S370: ARCH_MISSING("EM_S370");
case EM_PARISC: ARCH_MISSING("EM_PARISC");
case EM_PARISC:
return reloc_convert_parisc(bin, rel, GOT);
case EM_VPP500: ARCH_MISSING("EM_VPP500");
case EM_960: ARCH_MISSING("EM_960");
case EM_S390: ARCH_MISSING("EM_S390");

View file

@ -2011,6 +2011,47 @@ static void patch_reloc_alpha(RZ_INOUT RzBuffer *buf_patched, const ut64 patch_a
}
}
/**
* \brief Patches the opcode at a given address depending on the relocation type.
*
* NOTE: Some relocation symbols are not yet implemented
*
* \param buf_patched Buffer from which the opcode is read and the patched opcode is written to.
* \param patch_addr The address of the opcode being patched.
* \param rel_type The relocation type.
* \param big_endian The endianness - true if BE, false if LE
* \param fs Formular values to calculate the new relocation value.
*/
static void patch_reloc_parisc(RZ_INOUT RzBuffer *buf_patched, const ut64 patch_addr, const int rel_type, bool big_endian, RelocFormularSymbols *fs) {
rz_return_if_fail(buf_patched && fs);
ut8 buf[8] = { 0 };
ut64 val = 0;
switch (rel_type) {
default:
UNHANDL_DEF("PARISC", rel_type);
return;
case R_PARISC_COPY:
/* fall-thru */
case R_PARISC_NONE:
return;
case R_PARISC_DIR32: // S + A
rz_buf_read_at(buf_patched, patch_addr, buf, 4);
val = rz_read_ble32(buf, big_endian);
val += fs->S + fs->A;
rz_write_ble32(buf, val, big_endian);
rz_buf_write_at(buf_patched, patch_addr, buf, 4);
return;
case R_PARISC_DIR64: // S + A
rz_buf_read_at(buf_patched, patch_addr, buf, 8);
val = rz_read_ble64(buf, big_endian);
val += fs->S + fs->A;
rz_write_ble64(buf, val, big_endian);
rz_buf_write_at(buf_patched, patch_addr, buf, 8);
return;
}
}
#undef UNHANDL
#undef UNHANDL_DEF
@ -2080,13 +2121,15 @@ void Elf_(rz_bin_elf_patch_relocation)(RZ_NONNULL ELFOBJ *bin, RZ_NONNULL RzBinE
case EM_SPARCV9:
patch_reloc_sparc(bin->buf_patched, patch_addr, rel->type, big_endian, &formular_sym);
break;
case EM_PARISC:
patch_reloc_parisc(bin->buf_patched, patch_addr, rel->type, big_endian, &formular_sym);
break;
case EM_M32: ARCH_MISSING("EM_M32");
case EM_68K: ARCH_MISSING("EM_68K");
case EM_88K: ARCH_MISSING("EM_88K");
case EM_IAMCU: ARCH_MISSING("EM_IAMCU");
case EM_860: ARCH_MISSING("EM_860");
case EM_S370: ARCH_MISSING("EM_S370");
case EM_PARISC: ARCH_MISSING("EM_PARISC");
case EM_VPP500: ARCH_MISSING("EM_VPP500");
case EM_960: ARCH_MISSING("EM_960");
case EM_PPC: ARCH_MISSING("EM_PPC");

158
test/db/analysis/hppa Normal file
View file

@ -0,0 +1,158 @@
NAME=hppa analysis graph
FILE=bins/hppa/elf-Linux-hppa-bash
CMDS=<<EOF
aaa
s sym.array_keys_to_word_list
pdf
agf
EOF
EXPECT=<<EOF
/ sym.array_keys_to_word_list();
| 0x000607ec stw rp, -0x14(sp)
| 0x000607f0 stw,ma r5, 0x80(sp)
| 0x000607f4 ldo -0x70(sp), r1
| 0x000607f8 stw r4, -0x7c(sp)
| 0x000607fc stw r3, -0x78(sp)
| ,=< 0x00060800 movb,<> r26, r5, 0x6082c
| | 0x00060804 fstd,ma fr12, 8(r1)
| | 0x00060808 ldo 0(flags), r4
| | 0x0006080c or r4, flags, ret0
| | 0x00060810 ldo -0x70(sp), r1
| | 0x00060814 ldw -0x94(sp), rp
| | 0x00060818 ldw -0x7c(sp), r4
| | 0x0006081c ldw -0x78(sp), r3
| | 0x00060820 fldd,ma 8(r1), fr12
| | 0x00060824 bve (rp)
| | 0x00060828 ldw,mb -0x80(sp), r5
| `-> 0x0006082c ldw 0x10(r5), ret0
| 0x00060830 ldw 0x14(r5), r19
| 0x00060834 or,* ret0, r19, ret0
| 0x00060838 cmpib,= 0, ret0, 0x6080c
| 0x0006083c ldo 0(flags), r4
| 0x00060840 ldw 0x18(r5), ret0
| 0x00060844 ldw 0xc(ret0), r3
| 0x00060848 cmpb,= r3, ret0, 0x60810
| 0x0006084c or r4, flags, ret0
| 0x00060850 ldw 0(r3), r25
| 0x00060854 ldw 4(r3), r26
| 0x00060858 b,l sym.itos, flags
| 0x0006085c or flags, flags, flags
| 0x00060860 stw ret0, -0x10(sp)
| 0x00060864 fldw -0x10(sp), fr12
| 0x00060868 b,l sym.make_bare_word, r31
| 0x0006086c or ret0, flags, r26
| 0x00060870 or r4, flags, r25
| 0x00060874 b,l sym.make_word_list, r31
| 0x00060878 or ret0, flags, r26
| 0x0006087c fstw fr12, -0x10(sp)
| 0x00060880 ldw -0x10(sp), r26
| 0x00060884 b,l 0x76aec, flags
| 0x00060888 or ret0, flags, r4
| 0x0006088c ldw 0xc(r3), r3
| 0x00060890 ldw 0x18(r5), ret0
| 0x00060894 cmpb,<> r3, ret0, 0x60850
| 0x00060898 or flags, flags, flags
| 0x0006089c cmpib,= 0, r4, 0x60810
| 0x000608a0 or r4, flags, ret0
| 0x000608a4 ldw 0(r4), ret0
| 0x000608a8 cmpib,= 0, ret0, 0x6080c
| 0x000608ac or r4, flags, r26
| 0x000608b0 ldo -0x70(sp), r1
| 0x000608b4 ldw -0x94(sp), rp
| 0x000608b8 ldw -0x7c(sp), r4
| 0x000608bc ldw -0x78(sp), r3
| 0x000608c0 fldd,ma 8(r1), fr12
| 0x000608c4 b,l sym.list_reverse, flags
| 0x000608c8 ldw,mb -0x80(sp), r5
\ 0x000608cc or flags, flags, flags
.--------------------------------.
| 0x607ec |
| sym.array_keys_to_word_list(); |
| stw rp, -0x14(sp) |
| stw,ma r5, 0x80(sp) |
| ldo -0x70(sp), r1 |
| stw r4, -0x7c(sp) |
| stw r3, -0x78(sp) |
| movb,<> r26, r5, 0x6082c |
`--------------------------------'
f t
| |
| '--------------.
.------' |
| |
.----------------------. |
| 0x60804 | |
| fstd,ma fr12, 8(r1) | |
| ldo 0(flags), r4 | |
| or r4, flags, ret0 | |
| ldo -0x70(sp), r1 | |
| ldw -0x94(sp), rp | |
| ldw -0x7c(sp), r4 | |
| ldw -0x78(sp), r3 | |
| fldd,ma 8(r1), fr12 | |
| bve (rp) | |
| ldw,mb -0x80(sp), r5 | |
`----------------------' |
v |
| |
'------. |
| .--------------'
| |
.-----------------------------.
| 0x6082c |
| ldw 0x10(r5), ret0 |
| ldw 0x14(r5), r19 |
| or,* ret0, r19, ret0 |
| cmpib,= 0, ret0, 0x6080c |
| ldo 0(flags), r4 |
| ldw 0x18(r5), ret0 |
| ldw 0xc(ret0), r3 |
| cmpb,= r3, ret0, 0x60810 |
| or r4, flags, ret0 |
| ldw 0(r3), r25 |
| ldw 4(r3), r26 |
| b,l sym.itos, flags |
| or flags, flags, flags |
| stw ret0, -0x10(sp) |
| fldw -0x10(sp), fr12 |
| b,l sym.make_bare_word, r31 |
| or ret0, flags, r26 |
| or r4, flags, r25 |
| b,l sym.make_word_list, r31 |
| or ret0, flags, r26 |
| fstw fr12, -0x10(sp) |
| ldw -0x10(sp), r26 |
| b,l 0x76aec, flags |
| or ret0, flags, r4 |
| ldw 0xc(r3), r3 |
| ldw 0x18(r5), ret0 |
| cmpb,<> r3, ret0, 0x60850 |
| or flags, flags, flags |
| cmpib,= 0, r4, 0x60810 |
| or r4, flags, ret0 |
| ldw 0(r4), ret0 |
| cmpib,= 0, ret0, 0x6080c |
| or r4, flags, r26 |
| ldo -0x70(sp), r1 |
| ldw -0x94(sp), rp |
| ldw -0x7c(sp), r4 |
| ldw -0x78(sp), r3 |
| fldd,ma 8(r1), fr12 |
| b,l sym.list_reverse, flags |
| ldw,mb -0x80(sp), r5 |
| or flags, flags, flags |
`-----------------------------'
v
|
'.
|
.---------------------------.
| 0x608d0 |
| sym.array_to_word_list(); |
| stw rp, -0x14(sp) |
| stw,ma r5, 0x40(sp) |
| stw r4, -0x3c(sp) |
| movb,<> r26, r5, 0x60900 |
`---------------------------'
EOF
RUN

View file

@ -22,7 +22,7 @@ adAeI 16 gb LGPL3 GameBoy(TM) (z80-like) (by condret)
_dAeI 16 h8300 LGPL3 Hitachi/Renesas H8/300 disassembly plugin
_dA__ 16 h8500 LGPL3 Hitachi/Renesas H8/500 disassembler (by billow)
_dA_I 32 hexagon LGPL3 Qualcomm Hexagon (QDSP6) V6 (by Rot127)
_d___ 32 hppa GPL3 HP PA-RISC
_dA__ 32 64 hppa LGPL3 HP PA-RISC Capstone-based disassembler (by xvilka)
_dA__ 4 i4004 LGPL3 Intel 4004 disassembler
_dA__ 8 i8080 BSD Intel 8080 disassembler
adA__ 32 java LGPL-3 Java bytecode disassembler (by deroad)

File diff suppressed because one or more lines are too long

View file

@ -790,16 +790,16 @@ hppa7100
32
true
;-- section.0.__TEXT.__text:
0x000039e8 ldw 0(sp),r26 ; [00] -r-x section size 10588 named 0.__TEXT.__text
0x000039ec ldo 7f(sp),sp
0x000039f0 ldil L%3800,r1
0x000039f4 be 1fc(sr4,r1)
0x000039f8 depwi 0,31,6,sp
0x000039fc stw rp,-14(sp)
0x00003a00 stw,ma r5,80(sp)
0x00003a04 stw r4,-7c(sp)
0x00003a08 stw r3,-78(sp)
0x00003a0c copy r26,r4
0x000039e8 ldw 0(sp), r26 ; [00] -r-x section size 10588 named 0.__TEXT.__text
0x000039ec ldo 0x7f(sp), sp
0x000039f0 ldil 0x3800, r1
0x000039f4 be 0x1fc(sr4,r1)
0x000039f8 depi 0, 0x1f, 6, sp
0x000039fc stw rp, -0x14(sp)
0x00003a00 stwm r5, 0x80(sp)
0x00003a04 stw r4, -0x7c(sp)
0x00003a08 stw r3, -0x78(sp)
0x00003a0c or r26, flags, r4
Backed up flag space into 'flags.sparc_32_all.sdb'. You can restore the flags with the 'ko' command.
sparc
all