diff --git a/include/fxos/function.h b/include/fxos/function.h index 7841978..00ef895 100644 --- a/include/fxos/function.h +++ b/include/fxos/function.h @@ -80,7 +80,7 @@ struct Function: public BinaryObject /* Construction functions to be used only by the cfg pass. */ void exploreFunctionAt(u32 address); - void addBasicBlock(BasicBlock &&bb); + BasicBlock &addBasicBlock(BasicBlock &&bb); void updateFunctionSize(); private: diff --git a/include/fxos/lang.h b/include/fxos/lang.h index 02cfefd..e9ebe86 100644 --- a/include/fxos/lang.h +++ b/include/fxos/lang.h @@ -127,6 +127,10 @@ struct AsmArgument /* Immediate value. Valid for Imm */ int imm; }; + + /* Get the PC-relative target, assuming the instruction is at the provided + address, for arguments with PC-relative offsets. */ + u32 getPCRelativeTarget(u32 pc, int size) const; }; /* AsmArgument constructors */ diff --git a/lib/function.cpp b/lib/function.cpp index 319be59..0fec445 100644 --- a/lib/function.cpp +++ b/lib/function.cpp @@ -19,13 +19,14 @@ Function::Function(Binary &binary, u32 address): /* Size is not determined at first. */ /* Default unambiguous name */ - setName(format("FUN_%08x", address)); + setName(format("fun.%08x", address)); } /* Add a basic block to the function. The entry block must be added first. */ -void Function::addBasicBlock(BasicBlock &&bb) +BasicBlock &Function::addBasicBlock(BasicBlock &&bb) { m_blocks.push_back(bb); + return m_blocks.back(); } /* Update the function's BinaryObject size by finding the last address covered @@ -181,11 +182,12 @@ void Function::exploreFunctionAt(u32 functionAddress) for(u32 _: b.leaders) { (void)_; - BasicBlock bb(*this, *it, *it == functionAddress); + BasicBlock bb0(*this, *it, *it == functionAddress); + BasicBlock &bb = addBasicBlock(std::move(bb0)); do { // TODO: Support 32-bit instructions - u32 opcode = parentBinary().vspace().read_u16(opcode); + u32 opcode = parentBinary().vspace().read_u16(*it); Instruction ins(*this, *it, opcode); bb.addInstruction(std::move(ins)); it++; @@ -193,7 +195,6 @@ void Function::exploreFunctionAt(u32 functionAddress) while(it != b.addresses.end() && !b.leaders.count(*it)); bb.finalizeBlock(); - addBasicBlock(std::move(bb)); } } @@ -216,7 +217,7 @@ uint BasicBlock::blockIndex() const if(&bb == this) return i; } - assert(false && "blockIndex of block not in its own parent"); + assert(false && "blockIndex: block not in its own parent"); } bool BasicBlock::mayFallthrough() const diff --git a/lib/lang.cpp b/lib/lang.cpp index 7010698..8cc83df 100644 --- a/lib/lang.cpp +++ b/lib/lang.cpp @@ -173,6 +173,24 @@ std::string AsmArgument::str() const } } +u32 AsmArgument::getPCRelativeTarget(u32 pc, int size) const +{ + size = size + (size == 0); + + if(this->kind == AsmArgument::PcRel) + return (pc & -size) + 4 + this->disp; + if(this->kind == AsmArgument::PcJump) + return pc + 4 + this->disp; + if(this->kind == AsmArgument::PcAddr) + return (pc & -4) + 4 + this->disp; + + /* SH3 manual says that mova uses the target address of the jump when + in a delay slot. SH4AL-DSP makes it invalid. Supporting this would + be very tricky since the target PC is often dynamic (eg. rts). */ + + return 0xffffffff; +} + //--- // Instruction management //--- @@ -219,20 +237,10 @@ AsmInstruction::AsmInstruction( u32 AsmInstruction::getPCRelativeTarget(u32 pc) const { - for(int i = 0; i < arg_count; i++) { - AsmArgument const &arg = args[i]; - int size = opsize + (opsize == 0); - - if(arg.kind == AsmArgument::PcRel) - return (pc & -size) + 4 + arg.disp; - if(arg.kind == AsmArgument::PcJump) - return pc + 4 + arg.disp; - if(arg.kind == AsmArgument::PcAddr) - return (pc & -4) + 4 + arg.disp; - - /* SH3 manual says that mova uses the target address of the jump when - in a delay slot. SH4AL-DSP makes it invalid. Supporting this would - be very tricky since the target PC is often dynamic (eg. rts). */ + for(int i = 0; i < this->arg_count; i++) { + u32 target = this->args[i].getPCRelativeTarget(pc, this->opsize); + if(target != 0xffffffff) + return target; } return 0xffffffff; } diff --git a/lib/view/assembly.cpp b/lib/view/assembly.cpp index a8f4e08..c36bd90 100644 --- a/lib/view/assembly.cpp +++ b/lib/view/assembly.cpp @@ -7,11 +7,13 @@ #include #include +#include #include #include #include #include #include +#include #include namespace FxOS { @@ -32,7 +34,8 @@ static inline bool output(ArgumentOutput &out, ViewAssemblyOptions::Promotion p, return false; } -static void renderArgument(AsmArgument const &arg, Argument const &a, +// TODO: Take advantage of Instruction's info +static void renderArgument(AsmArgument const &arg, u32 pc, int opsize, ArgumentOutput &out, ViewAssemblyOptions const &opts) { out.push_back({{}, arg.str()}); @@ -50,30 +53,51 @@ static void renderArgument(AsmArgument const &arg, Argument const &a, else if(arg.kind == AsmArgument::PcAddr) type = PCAddr; + u32 value; + bool hasValue = false; + OS *os = opts.binary ? opts.binary->OSAnalysis() : nullptr; + VirtualSpace *vspace = opts.binary ? &opts.binary->vspace() : nullptr; + if(type == PCJump || type == PCRelative || type == PCAddr) { auto p = (type == PCJump) ? opts.promotions.PCJump_to_Location : (type == PCAddr) ? opts.promotions.PCAddr_to_Location : opts.promotions.PCRelative_to_Location; - if(!RelConstDomain().is_constant(a.location)) + u32 location = arg.getPCRelativeTarget(pc, opsize); + if(location == 0xffffffff) return; - if(output(out, p, {}, format("<%s>", a.location.str()))) + if(output(out, p, {}, format("<%08x>", location))) return; type = (type == PCRelative) ? Location : Constant; + + // TODO: Check that this is a read operation! + if(opsize != 0 && vspace && vspace->covers(location, opsize)) { + if(opsize == 1) { + value = vspace->read_i8(location); + hasValue = true; + } + if(opsize == 2) { + value = vspace->read_i16(location); + hasValue = true; + } + if(opsize == 4) { + value = vspace->read_i32(location); + hasValue = true; + } + } } if(type == Location) { - // TODO: Check that this is a read operation! auto p = opts.promotions.ReadLocation_to_Constant; - if(!a.value || output(out, p, {}, a.value.str())) + auto rc = RelConstDomain().constant(value); + if(!hasValue || output(out, p, {}, rc.str())) return; type = Constant; } /* Promote to object name first if available... */ - if(type == Constant && a.value && opts.binary) { + if(type == Constant && hasValue && opts.binary) { auto p = opts.promotions.Constant_to_ObjectName; - u32 address = RelConstDomain().constant_value(a.value); - BinaryObject *obj = opts.binary->objectAt(address); + BinaryObject *obj = opts.binary->objectAt(value); if(obj) { if(output(out, p, {}, obj->name())) @@ -82,11 +106,14 @@ static void renderArgument(AsmArgument const &arg, Argument const &a, } } /* ... or, as a default, a syscall number */ - if(type == Constant && a.value && a.syscall_id >= 0) { - auto p = opts.promotions.Constant_to_SyscallNumber; - if(output(out, p, {}, format("%%%04x", a.syscall_id))) - return; - type = SyscallNumber; + if(type == Constant && hasValue && os) { + int syscall_id = os->find_syscall(value); + if(syscall_id >= 0) { + auto p = opts.promotions.Constant_to_SyscallNumber; + if(output(out, p, {}, format("%%%04x", syscall_id))) + return; + type = SyscallNumber; + } } } @@ -133,7 +160,7 @@ static void doOldInst(u32 pc, OldInstruction &i, if(n) printf(", "); - renderArgument(i.inst->args[n], i.args[n], argout, opts); + renderArgument(i.inst->args[n], pc, i.inst->opsize, argout, opts); for(size_t i = 0; i < argout.size(); i++) { if(i != 0) @@ -201,11 +228,109 @@ void viewAssemblyLegacyAddress( //=== Binary-API assembly printer ===// -void viewAssemblyInstruction( - Instruction const &inst, ViewAssemblyOptions *opts); +static ViewAssemblyOptions defaultOptions {}; -void viewAssemblyBasicBlock(BasicBlock const &bb, ViewAssemblyOptions *opts); +void viewAssemblyInstruction(Instruction const &ins, ViewAssemblyOptions *opts) +{ + opts = opts ? opts : &defaultOptions; -void viewAssemblyFunction(Function const &fun, ViewAssemblyOptions *opts); + AsmInstruction opcode = ins.opcode(); + ArgumentOutput argout; + u32 pc = ins.address(); + + printf(" %08x: %04x", pc, opcode.opcode); + + /* Mnemonic */ + static char const *suffixes[5] = {"", ".b", ".w", "", ".l"}; + char const *suffix = suffixes[(opcode.opsize <= 4) ? opcode.opsize : 0]; + + int spacing + = opcode.arg_count ? 8 - strlen(opcode.mnemonic) - strlen(suffix) : 0; + printf(" %s%s%*s", opcode.mnemonic, suffix, spacing, ""); + + /* Arguments */ + for(size_t n = 0; n < opcode.arg_count; n++) { + if(n) + printf(", "); + + renderArgument(opcode.args[n], pc, opcode.opsize, argout, *opts); + + for(size_t i = 0; i < argout.size(); i++) { + if(i != 0) + printf(" "); + printf("%s", argout[i].second.c_str()); + } + + argout.clear(); + } + + printf("\n"); +} + +static std::string objectsAt( + Binary const &binary, u32 address, BinaryObject const *except = nullptr) +{ + std::vector objects; + + OS *os = binary.OSAnalysis(); + if(os) { + int index = os->find_syscall(address); + if(index >= 0) + objects.push_back(fmt::format("%{:04x}", index)); + } + + int unnamed = 0; + for(BinaryObject const *obj: binary.objectsAt(address)) { + if(obj == except) + continue; + if(obj->name() == "") + unnamed++; + else + objects.push_back(obj->name()); + } + + if(unnamed > 0) + objects.push_back(fmt::format("+{}", unnamed)); + + return std::accumulate(objects.begin(), objects.end(), std::string {}, + [](auto &l, auto const r) { return l + (l.empty() ? "" : " ") + r; }); +} + +void viewAssemblyBasicBlock(BasicBlock const &bb, ViewAssemblyOptions *opts) +{ + opts = opts ? opts : &defaultOptions; + + printf(" bb.%08x", bb.address()); + if(bb.address() != bb.parentFunction().address()) { + std::string others = objectsAt(bb.parentBinary(), bb.address()); + if(others != "") + printf(" (%s)", others.c_str()); + } + printf(":\n"); + + for(Instruction const &ins: bb) + viewAssemblyInstruction(ins, opts); + + printf("\n"); +} + +void viewAssemblyFunction(Function const &fun, ViewAssemblyOptions *opts) +{ + opts = opts ? opts : &defaultOptions; + + /* Note that the Function constructor sets "fun.%08x" as name by default */ + if(fun.name() != "") + printf("%s", fun.name().c_str()); + else + printf("fun.%08x", fun.address()); + + std::string others = objectsAt(fun.parentBinary(), fun.address(), &fun); + if(others != "") + printf(" (%s)", others.c_str()); + printf(":\n"); + + for(BasicBlock const &bb: fun) + viewAssemblyBasicBlock(bb, opts); +} } /* namespace FxOS */ diff --git a/shell/d.cpp b/shell/d.cpp index f9d1c0b..8ec4e80 100644 --- a/shell/d.cpp +++ b/shell/d.cpp @@ -39,7 +39,7 @@ static void disassemble( } } else if(pass == "print" && address + 1) { - viewAssemblyLegacyAddress(binary, address); + // viewAssemblyLegacyAddress(binary, address); } else { FxOS_log(ERR, "unknown pass <%s>", pass); @@ -56,9 +56,11 @@ static void disassemble( } if(address + 1) { - printf("&<<<< function test >>>>&\n"); Function f(binary, address); f.exploreFunctionAt(address); + ViewAssemblyOptions opts; + opts.binary = &binary; + viewAssemblyFunction(f, &opts); } } diff --git a/shell/parser.cpp b/shell/parser.cpp index 996da78..391133d 100644 --- a/shell/parser.cpp +++ b/shell/parser.cpp @@ -294,7 +294,7 @@ void Parser::accept_options() if(m_options.count(opt)) m_options[opt](*this); else if(!m_complete) - FxOS_log(ERR, "unrecognized option %s", opt); + lex_err("unrecognized option %s", opt.c_str()); } } @@ -316,11 +316,12 @@ long Parser::atom() if(opt) val = *opt; else if(!m_complete) - FxOS_log(ERR, "symbol '%s' is undefined", t.value.STRING); + lex_err("symbol '%s' is undefined", t.value.STRING.c_str()); + } + else if(!m_complete) { + lex_err("cannot query symbol '%s' (no virtual space)", + t.value.STRING.c_str()); } - else if(!m_complete) - FxOS_log(ERR, "cannot query symbol '%s' (no virtual space)", - t.value.STRING); return val; } else if(t.type == T::SYSCALL) {