408 lines
11 KiB
C++
408 lines
11 KiB
C++
|
|
// Loader for the Macro-assembler-related binary format
|
|
|
|
#include "../idaldr.h"
|
|
#include "mas.hpp"
|
|
|
|
// undefine this to print some debugging information
|
|
// in the IDA console.
|
|
//#define DEBUG
|
|
|
|
//-----------------------------------------------------------------------------
|
|
struct gas_family
|
|
{
|
|
uchar code;
|
|
const char *processor; //lint !e958 padding is required to align members
|
|
};
|
|
|
|
static const struct gas_family families[] =
|
|
{
|
|
{ 0x01, "68k" },
|
|
// 0x03 : M*Core
|
|
{ 0x05, "ppc" },
|
|
{ 0x09, "dsp56k" },
|
|
{ 0x11, "m740" },
|
|
// 0x12 : MELPS-4500
|
|
// 0x13 : M16
|
|
// 0x14 : M16C
|
|
// 0x15 : F2MC8L
|
|
{ 0x16, "f2mc16l" },
|
|
{ 0x19, "m7700" },
|
|
// 0x21 : MCS-48
|
|
// 0x25 : SYM53C8xx
|
|
// 0x29 : 29xxx
|
|
{ 0x2A, "i960b" }, // little or big endian ????
|
|
// 0x31 : MCS-51
|
|
{ 0x32, "st9" },
|
|
{ 0x33, "st7" },
|
|
// 0x38 : 1802/1805
|
|
// 0x39 : MCS-96/196/296
|
|
// 0x3A : 8X30x
|
|
{ 0x3B, "avr" },
|
|
// 0x3C : XA
|
|
// 0x3F : 4004/4040
|
|
{ 0x41, "8085" },
|
|
{ 0x42, "8086" },
|
|
{ 0x47, "tms320c6" },
|
|
// 0x48 : TMS9900
|
|
// 0x49 : TMS370xxx
|
|
// 0x4A : MSP430
|
|
{ 0x4B, "tms32054" },
|
|
{ 0x4C, "c166" },
|
|
{ 0x51, "z80" },
|
|
// 0x52 : TLCS-900
|
|
// 0x53 : TLCS-90
|
|
// 0x54 : TLCS-870
|
|
// 0x55 : TLCS-47
|
|
// 0x56 : TLCS-9000
|
|
{ 0x61, "6800" },
|
|
{ 0x62, "6805" },
|
|
{ 0x63, "6809" },
|
|
// 0x64 : 6804
|
|
// 0x65 : 68HC16
|
|
// 0x66 : 68HC12
|
|
// 0x67 : ACE
|
|
{ 0x68, "h8300" },
|
|
{ 0x69, "h8500" },
|
|
// 0x6C : SH7000
|
|
// 0x6C : SC14xxx
|
|
// 0x6C : SC/MP
|
|
// 0x6C : COP8
|
|
{ 0x70, "pic16cxx" },
|
|
{ 0x71, "pic16cxx" },
|
|
// 0x72 : PIC17C4x
|
|
// 0x73 : TMS-7000
|
|
// 0x74 : TSM3201x
|
|
// 0x75 : TSM320C2x
|
|
// 0x76 : TSM320C3x
|
|
{ 0x77, "tms320c2" },
|
|
// 0x78 : ST6uPD772
|
|
{ 0x79, "z8" }
|
|
// 0x7A : uPD78(C)10
|
|
// 0x7B : 75K0
|
|
// 0x7C : 78K0
|
|
// 0x7D : uPD7720
|
|
// 0x7E : uPD7725
|
|
// 0x7F : uPD77230
|
|
};
|
|
|
|
static char creator[MAXSTR]; // program name which created the binary
|
|
static int entry_point; // address of the entry point
|
|
static const char *set_proc = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// output an error and exit loader.
|
|
AS_PRINTF(1, 2) NORETURN static void mas_error(const char *format, ...)
|
|
{
|
|
char b[MAXSTR];
|
|
va_list va;
|
|
|
|
va_start(va, format);
|
|
qvsnprintf(b, sizeof(b), format, va);
|
|
va_end(va);
|
|
loader_failure("mas loader critical error: %s", b);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// set the current processor type according to "cpu_type".
|
|
static bool mas_set_cpu(uchar cpu_type)
|
|
{
|
|
for ( int i = 0; i < qnumber(families); i++ )
|
|
{
|
|
if ( families[i].code != cpu_type )
|
|
continue;
|
|
|
|
const char *proc = families[i].processor;
|
|
if ( set_proc != NULL && !streq(proc, set_proc) )
|
|
mas_error("only one processor record is allowed");
|
|
|
|
set_proc = proc;
|
|
set_processor_type(proc, SETPROC_LOADER);
|
|
#if defined(DEBUG)
|
|
msg("MAS: detected processor %s\n", proc);
|
|
#endif
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// return a segment name according to its "segment_type".
|
|
static const char *mas_get_segname(uchar segment_type)
|
|
{
|
|
switch ( segment_type )
|
|
{
|
|
case 0x00: return "UNDEFINED";
|
|
case 0x01: return "CODE";
|
|
case 0x02: return "DATA";
|
|
case 0x03: return "IDATA";
|
|
case 0x04: return "XDATA";
|
|
case 0x05: return "YDATA";
|
|
case 0x06: return "BDATA";
|
|
case 0x07: return "IO";
|
|
case 0x08: return "REG";
|
|
case 0x09: return "ROMDATA";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// write comments.
|
|
static void mas_write_comments(void)
|
|
{
|
|
create_filename_cmt();
|
|
|
|
char entry_point_str[20];
|
|
if ( entry_point == -1 )
|
|
qstrncpy(entry_point_str, "NOT DETECTED", sizeof(entry_point_str));
|
|
else
|
|
qsnprintf(entry_point_str, sizeof(entry_point_str), "0x%X", entry_point);
|
|
|
|
// write name of the creator program
|
|
add_pgm_cmt("Creator program : %s", creator);
|
|
// write address of the entry point
|
|
add_pgm_cmt("Entry point : %s", entry_point_str);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// detect macro assembler files using the start sequence.
|
|
static int idaapi accept_file(
|
|
qstring *fileformatname,
|
|
qstring *, // too difficult to determine the processor
|
|
linput_t *li,
|
|
const char *)
|
|
{
|
|
// read the first word
|
|
uint16 word = 0;
|
|
if ( qlread(li, &word, 2) != 2 )
|
|
return 0;
|
|
|
|
#if defined(DEBUG)
|
|
msg("MAS: 2 first bytes : 0x%X\n", word);
|
|
#endif
|
|
|
|
// first word must match the start_sequence
|
|
if ( word != START_SEQUENCE )
|
|
return 0;
|
|
|
|
*fileformatname = "Macro Assembler by Alfred Arnold";
|
|
#if defined(DEBUG)
|
|
msg("MAS: detected mas binary file !\n");
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void load_bytes(linput_t *li, ea_t ea, asize_t size, const char *segname)
|
|
{
|
|
// validate the segment size
|
|
ea_t end = ea + size;
|
|
qoff64_t curpos = qltell(li);
|
|
qoff64_t endpos = curpos + size;
|
|
if ( ea == BADADDR || end < ea || endpos < curpos || endpos > qlsize(li) )
|
|
mas_error("wrong or too big segment %a..%a", ea, end);
|
|
|
|
// send code in the database
|
|
file2base(li, curpos, ea, end, FILEREG_PATCHABLE);
|
|
|
|
// set selector
|
|
sel_t selector = allocate_selector(0);
|
|
|
|
// create data segment
|
|
add_segm(selector, ea, end, segname, segname, ADDSEG_SPARSE);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void check_target_processor()
|
|
{
|
|
if ( PH.id == -1 )
|
|
loader_failure("Failed to determine the target processor, please specify it manually");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// process a file record according to its "record_type".
|
|
// return true if there is no more records to process.
|
|
static bool process_record(linput_t *li, const uchar record_type, bool load)
|
|
{
|
|
bool finished = false;
|
|
|
|
switch ( record_type )
|
|
{
|
|
// A record with a header byte of $81 is a record that may contain code or
|
|
// data from arbitrary segments.
|
|
//
|
|
// header : 1 byte
|
|
// segment : 1 byte
|
|
// gran : 1 byte
|
|
// start_addr : 4 bytes (entry point)
|
|
// length : 2 bytes
|
|
// data : length bytes
|
|
case 0x81:
|
|
{
|
|
mas_header_t header;
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
// read the header
|
|
if ( qlread(li, &header, sizeof(header)) != sizeof(header) )
|
|
mas_error("unable to read header (%" FMT_Z " bytes)", sizeof(header));
|
|
|
|
// granularities that differ from 1 are rare and mostly appear
|
|
// in DSP CPU's that are not designed for byte processing.
|
|
if ( header.gran != 1 )
|
|
mas_error("unsupported granularity (%d)", header.gran);
|
|
|
|
// set processor
|
|
if ( !mas_set_cpu(header.header) )
|
|
mas_error("processor type '0x%X' is currently unsupported", header.header);
|
|
if ( !load ) // we have the processor, nothing else to do
|
|
{
|
|
finished = true;
|
|
break;
|
|
}
|
|
|
|
// get segment name
|
|
const char *segname = mas_get_segname(header.segment);
|
|
if ( segname == NULL )
|
|
mas_error("invalid segment '0x%X'", header.segment);
|
|
|
|
#if defined(DEBUG)
|
|
msg("MAS: ready to read %d bytes (0x%X -> 0x%X)\n",
|
|
header.length, header.start_addr, header.start_addr + header.length);
|
|
#endif
|
|
load_bytes(li, header.start_addr, header.length, segname);
|
|
}
|
|
break;
|
|
|
|
// The last record in a file bears the Header $00 and has only a string as
|
|
// data field. This string does not have an explicit length specification;
|
|
// its end is equal to the file's end.
|
|
//
|
|
// The string contains only the name of the program that created the file
|
|
// and has no further meaning.
|
|
//
|
|
// creator : x bytes
|
|
case 0x00:
|
|
{
|
|
uint32 length = qlsize(li) - qltell(li);
|
|
#if defined(DEBUG)
|
|
msg("MAS: creator length : %ld bytes\n", length);
|
|
#endif
|
|
if ( length >= sizeof(creator) )
|
|
mas_error("creator length is too large (%u >= %" FMT_Z,
|
|
length, sizeof(creator));
|
|
ssize_t tmp = qlread(li, creator, length);
|
|
if ( tmp != length )
|
|
mas_error("unable to read creator string (i read %" FMT_ZS")", tmp);
|
|
creator[length] = '\0';
|
|
}
|
|
finished = true;
|
|
break;
|
|
|
|
// entry_point : 4 bytes
|
|
case 0x80:
|
|
{
|
|
if ( qlread(li, &entry_point, 4) != 4 )
|
|
mas_error("unable to read entry_point");
|
|
if ( load )
|
|
{
|
|
#if defined(DEBUG)
|
|
msg("MAS: detected entry point : 0x%X\n", entry_point);
|
|
#endif
|
|
inf_set_start_ip(entry_point); // entry point
|
|
segment_t *s = getseg(entry_point);
|
|
inf_set_start_cs(s ? s->sel : 0); // selector of code
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// start_addr : 4 bytes
|
|
// length : 2 bytes
|
|
// data : length bytes
|
|
if ( record_type >= 0x01 && record_type <= 0x7F )
|
|
{
|
|
check_target_processor();
|
|
|
|
struct
|
|
{
|
|
int start_addr;
|
|
short length;
|
|
} header;
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
// read the header
|
|
if ( qlread(li, &header, sizeof(header)) != sizeof(header) )
|
|
mas_error("unable to read header (%" FMT_Z " bytes)", sizeof(header));
|
|
|
|
if ( load )
|
|
load_bytes(li, header.start_addr, header.length, "DATA");
|
|
else
|
|
qlseek(li, qltell(li)+header.length);
|
|
}
|
|
else
|
|
{
|
|
mas_error("invalid record type '0x%X'\n", record_type);
|
|
}
|
|
}
|
|
return finished;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// load a macro assembler file in IDA.
|
|
void idaapi load_file(linput_t *li, ushort /*neflag*/, const char * /*fileformatname*/)
|
|
{
|
|
// already read the 2 first bytes
|
|
qlseek(li, 2);
|
|
|
|
// initialize static variables
|
|
qstrncpy(creator, "UNKNOWN", sizeof(creator));
|
|
entry_point = -1;
|
|
|
|
bool finished = false;
|
|
while ( !finished )
|
|
{
|
|
uchar record_type = 0;
|
|
|
|
// read the record type
|
|
if ( qlread(li, &record_type, 1) != 1 )
|
|
mas_error("unable to read the record type");
|
|
|
|
finished = process_record(li, record_type, true);
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
msg("MAS: reading complete\n");
|
|
#endif
|
|
|
|
check_target_processor();
|
|
|
|
mas_write_comments();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Loader description block
|
|
loader_t LDSC =
|
|
{
|
|
IDP_INTERFACE_VERSION,
|
|
0, // loader flags
|
|
//
|
|
// check input file format. if recognized, then return 1
|
|
// and fill 'fileformatname'.
|
|
// otherwise return 0
|
|
//
|
|
accept_file,
|
|
//
|
|
// load file into the database.
|
|
//
|
|
load_file,
|
|
//
|
|
// create output file from the database.
|
|
// this function may be absent.
|
|
//
|
|
NULL,
|
|
// take care of a moved segment (fix up relocations, for example)
|
|
NULL,
|
|
NULL,
|
|
};
|