485 lines
15 KiB
C
485 lines
15 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2022 Vincent DEFERT. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/**
|
|
* This program automates the procedure explained below and generates
|
|
* on the standard output the content of the 'models' list of models.py.
|
|
*
|
|
* It takes the name of the stc-isp executable as argument.
|
|
*/
|
|
/**
|
|
* Manual procedure to read MCU definitions from a new STC-ISP executable
|
|
* ========================================================================
|
|
*
|
|
* We want to extract 2 tables from the executable, one with MCU names and
|
|
* the other with their characteristics, let's call them "Name Table" and
|
|
* "Info Table" respectively.
|
|
*
|
|
* The Info Table appears first in the executable and contains references
|
|
* to the MCU name in the Name Table. Each entry in the Name Table is 16
|
|
* bytes long, 32 for the Info Table. New entries are prepended to the
|
|
* Info Table, and appended to the Name Table. Of course, both have the
|
|
* same number of entries.
|
|
*
|
|
* This means that the Name Table is very easy to locate, as well as the
|
|
* end of the Info Table, but not its beginning, which must be calculated.
|
|
*
|
|
* Finally, the field of an Info Table entry that references the MCU name
|
|
* is expressed as a memory address, not a file position, so we'll need to
|
|
* determine the base memory address of the name table.
|
|
*
|
|
* 1. Dump the content of the executable in a text file.
|
|
*
|
|
* hexdump -C stc-isp-v6.89G.exe > stc-isp-v6.89G.txt
|
|
*
|
|
* 2. Locate the first entry of the Name Table.
|
|
*
|
|
* Search for the following byte sequence:
|
|
* 53 54 43 39 30 4c 45 35 31 36 41 44 00 00 00 00
|
|
* (i.e. nul-terminated "STC90LE516AD" string).
|
|
*
|
|
* Let's call this file position NTS (Name Table Start).
|
|
*
|
|
* 3. Locate the end of the Name Table.
|
|
*
|
|
* Search for the following byte sequence:
|
|
* 55 4e 4b 4e 4f 57 4e 00 25 30 36 58 00 00 00 00
|
|
* (i.e. nul-terminated "UNKNOWN" and "%06X" strings).
|
|
*
|
|
* Let's call this file position NTE (Name Table End).
|
|
*
|
|
* 4. Find the end of the Info Table.
|
|
*
|
|
* Search for the following byte sequence (fixed last entry):
|
|
* 05 46 01 00 xx xx xx xx 90 f1 00 00 00 f8 00 00
|
|
* 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00
|
|
*
|
|
* Bytes marked as 'xx' must be ignored while searching
|
|
*
|
|
* [Note: searching for '90 f1 00 00 00 f8 00 00' is sufficient.]
|
|
*
|
|
* It should be followed by 32 zeroed bytes. Let's call the file position
|
|
* of the first zeroed byte ITE (Info Table End).
|
|
*
|
|
* 5. Find the beginning of the Info Table.
|
|
*
|
|
* The Info Table start with a block of 32 zeroed bytes except bytes
|
|
* 4-7 which point at NTE, i.e. an info block pointing at the 'UNKNOWN'
|
|
* MCU name. It's the only reliable way to determine the location of
|
|
* the Info Table.
|
|
*
|
|
* Our first valid info block will thus be the offset of the Unknown
|
|
* block + 32. Let's call this file position ITS (Info Table Start).
|
|
*
|
|
* 6. Calculate the number of MCU definitions (i.e. Info Table entries).
|
|
*
|
|
* NB_MCU = (ITE - ITS) / 32
|
|
*
|
|
* 7. Determine the base memory address of the name table.
|
|
*
|
|
* Let's suppose 'xx xx xx xx' is '9c f7 4a 00'. As it belongs to the Info
|
|
* Table entry describing the first item of the Name Table, we directly
|
|
* have what we're looking for, i.e. 0x004af79c.
|
|
*
|
|
* NTBA = littleEndianOf32bitUnsignedInt('xx xx xx xx')
|
|
*
|
|
* The index in the Name Table corresponding to a given Info Table item
|
|
* is thus:
|
|
*
|
|
* NAME_IDX = (nameAddressFieldOfInfoTableItem - NTBA) / 0x10
|
|
*
|
|
* NOTE: for some reason, the Info Table entries of the STC08XE-3V and
|
|
* STC08XE-5V each have 2 distinct mcuId, which gives 1115 Info Table
|
|
* entries for 1113 strings in the Name Table.
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// Must be updated with the "UNKNOWN" name offset before use.
|
|
static uint8_t infoTableStartSignature[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
// 0x90, 0xf1 is the magic number of the STC90LE516AD
|
|
// We test only the last 24 byte of its 32-byte entry, as they are
|
|
// sufficiently discriminating and do not depend on a particular
|
|
// executable release.
|
|
static const uint8_t infoTableEndSignature[] = {
|
|
0x90, 0xf1, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
// NUL-terminated "STC90LE516AD" followed by 3 NUL bytes
|
|
static const uint8_t nameTableStartSignature[] = {
|
|
0x53, 0x54, 0x43, 0x39, 0x30, 0x4c, 0x45, 0x35,
|
|
0x31, 0x36, 0x41, 0x44, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
// NUL-terminated "UNKNOWN" and "%06X" followed by 3 NUL bytes
|
|
static const uint8_t nameTableEndSignature[] = {
|
|
0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x00,
|
|
0x25, 0x30, 0x36, 0x58, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t flags;
|
|
uint32_t nameAddr;
|
|
uint32_t mcuId;
|
|
uint32_t flashSize;
|
|
uint32_t eepromSize;
|
|
uint32_t eepromStartAddr; // STC89 & STC90 only. 0 means IAP.
|
|
uint32_t totalSize;
|
|
uint32_t unknown2;
|
|
} MCUInfo;
|
|
|
|
// Bit 1 is 1 for MCU which can accept 5V power supply voltage, be it
|
|
// exclusively or not, and 0 for low-voltage only MCU (around 3.3V).
|
|
#define FLAG_ACCEPT_5V_SUPPLY_VOLTAGE 0x00000002
|
|
|
|
// Bit 3 is 1 for so-called "IAP" MCU, meaning the start address of the
|
|
// flash portion used for EEPROM emulation can be configured.
|
|
#define FLAG_CONFIGURABLE_EEPROM_SIZE 0x00000008
|
|
|
|
// Bit 7 is 1 for MCU with an adjustable internal RC oscillator, i.e.
|
|
// that supports calibration. When bits 7 and 8 are both 0, the MCU has
|
|
// no IRCO at all (external crystal only).
|
|
#define FLAG_CONFIGURABLE_IRCO_FREQ 0x00000080
|
|
|
|
// Bit 8 is 1 for MCU with a fixed-frequency internal RC oscillator
|
|
// (the old IRC* models).
|
|
#define FLAG_FIXED_FREQUENCY_IRCO 0x00000100
|
|
|
|
// Bit 12 is 1 for MCS-251 MCU, i.e. with a flash size that can be
|
|
// larger than 64KB.
|
|
#define FLAG_IS_MCS251_MCU 0x00001000
|
|
|
|
#define SEARCH_BUFFER_LEN 8192
|
|
#define MCU_NAME_LEN 16
|
|
|
|
#define NO_MATCH -1
|
|
#define FOUND -2
|
|
|
|
// May help to guess the meaning of new flags as they are added.
|
|
|
|
static void toBits(uint32_t n, char *result) {
|
|
*result = '\0';
|
|
int pos = 0;
|
|
|
|
for (uint32_t mask = 0x80000000; mask; mask >>= 1, pos++) {
|
|
if (pos) {
|
|
strcat(result, ",");
|
|
}
|
|
|
|
if (n & mask) {
|
|
strcat(result, "1");
|
|
} else {
|
|
strcat(result, "0");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void printCSVHeader(FILE *csvFile) {
|
|
if (csvFile != NULL) {
|
|
fprintf(csvFile, "name,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,flags (hex),mcuId,flashSize,eepromSize,eepromStartAddr,totalSize,unknown2\n");
|
|
}
|
|
}
|
|
|
|
static void printCSVRow(FILE *csvFile, const MCUInfo *info, const char *name) {
|
|
char flags[64];
|
|
|
|
if (csvFile != NULL) {
|
|
toBits(info->flags, flags);
|
|
|
|
fprintf(
|
|
csvFile,
|
|
"%s,%s,0x%08x,0x%04x,%u,%u,0x%08x,%u,0x%08x\n",
|
|
name,
|
|
flags,
|
|
info->flags,
|
|
(uint16_t) info->mcuId,
|
|
info->flashSize,
|
|
info->eepromSize,
|
|
info->eepromStartAddr,
|
|
info->totalSize,
|
|
info->unknown2
|
|
);
|
|
}
|
|
}
|
|
|
|
static const char *toBool(uint32_t flags, uint32_t mask) {
|
|
return (flags & mask) ? "True" : "False";
|
|
}
|
|
|
|
static void printMCUModel(const MCUInfo *info, const char *name) {
|
|
printf(
|
|
" MCUModel(name='%s', magic=0x%04x, total=%u, code=%u, eeprom=%u, iap=%s, calibrate=%s, mcs251=%s),\n",
|
|
name,
|
|
(uint16_t) info->mcuId,
|
|
info->totalSize,
|
|
info->flashSize,
|
|
info->eepromSize,
|
|
toBool(info->flags, FLAG_CONFIGURABLE_EEPROM_SIZE),
|
|
toBool(info->flags, FLAG_CONFIGURABLE_IRCO_FREQ),
|
|
toBool(info->flags, FLAG_IS_MCS251_MCU)
|
|
);
|
|
}
|
|
|
|
static void printUsage(const char *pgmName) {
|
|
printf("Usage: %s <STC-ISP_executable> [<CSV_output_file>]\n", pgmName);
|
|
printf("\n");
|
|
printf("- STC-ISP_executable is the file from which MCU models must be extracted.\n");
|
|
printf("Their list will be printed on the standard output.\n");
|
|
printf("\n");
|
|
printf("- The optional CSV_output_file will receive the MCU flags detail of each model\n");
|
|
printf("to facilitate reverse engineering efforts.\n");
|
|
printf("\n");
|
|
printf("Example: %s stc-isp-v6.91Q.exe MCUFlags.csv > MCUModels.txt\n", pgmName);
|
|
}
|
|
|
|
int main(int argc, const char **argv) {
|
|
int rc = 1;
|
|
MCUInfo *infoTable = NULL;
|
|
char *nameTable = NULL;
|
|
int mcuCount = 0;
|
|
uint32_t infoTableStartOffset = 0;
|
|
uint32_t infoTableEndOffset = 0;
|
|
uint32_t nameTableStartOffset = 0;
|
|
uint32_t nameTableEndOffset = 0;
|
|
uint32_t baseAddr = 0;
|
|
int nameTableSize = 0;
|
|
|
|
if (argc < 2) {
|
|
fprintf(stderr, "ERROR: missing argument\n");
|
|
printUsage(argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
FILE *exeFile = fopen(argv[1], "rb");
|
|
FILE *csvFile = NULL;
|
|
|
|
if (exeFile != NULL) {
|
|
if (argc > 2) {
|
|
csvFile = fopen(argv[2], "wt");
|
|
}
|
|
|
|
rc = 2;
|
|
uint8_t *buffer = (uint8_t *) malloc(SEARCH_BUFFER_LEN);
|
|
|
|
if (buffer != NULL) {
|
|
rc = 3;
|
|
int infoTableEndMatch = NO_MATCH;
|
|
int nameTableStartMatch = NO_MATCH;
|
|
int nameTableEndMatch = NO_MATCH;
|
|
uint32_t fileOffset = 0;
|
|
int bytesRead = 0;
|
|
|
|
while ((bytesRead = fread(buffer, 1, SEARCH_BUFFER_LEN, exeFile)) != 0) {
|
|
for (int curByte = 0; curByte < SEARCH_BUFFER_LEN; curByte++) {
|
|
int noMatch = 1;
|
|
|
|
if (infoTableEndMatch > NO_MATCH) {
|
|
if (infoTableEndSignature[infoTableEndMatch + 1] == buffer[curByte]) {
|
|
infoTableEndMatch++;
|
|
noMatch = 0;
|
|
|
|
if (infoTableEndMatch == (sizeof(infoTableEndSignature) -1)) {
|
|
infoTableEndMatch = FOUND;
|
|
break;
|
|
}
|
|
} else {
|
|
infoTableEndMatch = NO_MATCH;
|
|
}
|
|
}
|
|
|
|
if (nameTableStartMatch > NO_MATCH) {
|
|
if (nameTableStartSignature[nameTableStartMatch + 1] == buffer[curByte]) {
|
|
nameTableStartMatch++;
|
|
noMatch = 0;
|
|
|
|
if (nameTableStartMatch == (sizeof(nameTableStartSignature) -1)) {
|
|
nameTableStartMatch = FOUND;
|
|
break;
|
|
}
|
|
} else {
|
|
nameTableStartMatch = NO_MATCH;
|
|
}
|
|
}
|
|
|
|
if (nameTableEndMatch > NO_MATCH) {
|
|
if (nameTableEndSignature[nameTableEndMatch + 1] == buffer[curByte]) {
|
|
nameTableEndMatch++;
|
|
noMatch = 0;
|
|
|
|
if (nameTableEndMatch == (sizeof(nameTableEndSignature) - 1)) {
|
|
nameTableEndMatch = FOUND;
|
|
break;
|
|
}
|
|
} else {
|
|
nameTableEndMatch = NO_MATCH;
|
|
}
|
|
}
|
|
|
|
if (noMatch) {
|
|
if (infoTableEndMatch == NO_MATCH && infoTableEndSignature[0] == buffer[curByte]) {
|
|
infoTableEndMatch = 0;
|
|
infoTableEndOffset = fileOffset + curByte;
|
|
} else if (nameTableStartMatch == NO_MATCH && nameTableStartSignature[0] == buffer[curByte]) {
|
|
nameTableStartMatch = 0;
|
|
nameTableStartOffset = fileOffset + curByte;
|
|
} else if (nameTableEndMatch == NO_MATCH && nameTableEndSignature[0] == buffer[curByte]) {
|
|
nameTableEndMatch = 0;
|
|
nameTableEndOffset = fileOffset + curByte;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (infoTableEndMatch == FOUND && nameTableStartMatch == FOUND && nameTableEndMatch == FOUND) {
|
|
rc = 0;
|
|
break;
|
|
}
|
|
|
|
fileOffset += SEARCH_BUFFER_LEN;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
// Point to the byte immediately following the table's last entry.
|
|
infoTableEndOffset += sizeof(infoTableEndSignature);
|
|
// Read last item of Info Table
|
|
fseek(exeFile, infoTableEndOffset - sizeof(MCUInfo), SEEK_SET);
|
|
MCUInfo lastItem;
|
|
fread(&lastItem, sizeof(MCUInfo), 1, exeFile);
|
|
// We need it now in order to calculate the memory address
|
|
// corresponding to the UNKNOWN name.
|
|
// We'll also need baseAddr later, anyway.
|
|
baseAddr = lastItem.nameAddr;
|
|
|
|
rc = 4;
|
|
int infoTableStartMatch = NO_MATCH;
|
|
uint32_t fileOffset = 0;
|
|
int bytesRead = 0;
|
|
*((uint32_t *)(infoTableStartSignature)) = (baseAddr - nameTableStartOffset) + nameTableEndOffset;
|
|
fseek(exeFile, 0, SEEK_SET);
|
|
|
|
while ((bytesRead = fread(buffer, 1, SEARCH_BUFFER_LEN, exeFile)) != 0) {
|
|
for (int curByte = 0; curByte < SEARCH_BUFFER_LEN; curByte++) {
|
|
if (infoTableStartMatch > NO_MATCH) {
|
|
if (infoTableStartSignature[infoTableStartMatch + 1] == buffer[curByte]) {
|
|
infoTableStartMatch++;
|
|
|
|
if (infoTableStartMatch == (sizeof(infoTableStartSignature) - 1)) {
|
|
infoTableStartMatch = FOUND;
|
|
break;
|
|
}
|
|
} else {
|
|
infoTableStartMatch = NO_MATCH;
|
|
}
|
|
}
|
|
|
|
if (infoTableStartMatch == NO_MATCH && infoTableStartSignature[0] == buffer[curByte]) {
|
|
infoTableStartMatch = 0;
|
|
infoTableStartOffset = fileOffset + curByte;
|
|
}
|
|
}
|
|
|
|
if (infoTableStartMatch == FOUND) {
|
|
// Point to the first entry following the Unknown one.
|
|
infoTableStartOffset += sizeof(MCUInfo) - 4;
|
|
// Calculate number of entries while we're at it
|
|
mcuCount = (infoTableEndOffset - infoTableStartOffset) / sizeof(MCUInfo);
|
|
rc = 0;
|
|
break;
|
|
}
|
|
|
|
fileOffset += SEARCH_BUFFER_LEN;
|
|
}
|
|
}
|
|
|
|
free(buffer);
|
|
|
|
if (rc == 0) {
|
|
nameTableSize = nameTableEndOffset - nameTableStartOffset;
|
|
|
|
nameTable = (char *) malloc(nameTableSize);
|
|
|
|
if (nameTable == NULL) {
|
|
rc = 5;
|
|
}
|
|
}
|
|
|
|
if (rc == 0) {
|
|
fseek(exeFile, nameTableStartOffset, SEEK_SET);
|
|
fread(nameTable, nameTableSize, 1, exeFile);
|
|
|
|
infoTable = (MCUInfo *) malloc(infoTableEndOffset - infoTableStartOffset);
|
|
|
|
if (infoTable != NULL) {
|
|
fseek(exeFile, infoTableStartOffset, SEEK_SET);
|
|
fread(infoTable, infoTableEndOffset - infoTableStartOffset, 1, exeFile);
|
|
|
|
} else {
|
|
rc = 6;
|
|
free(nameTable);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(exeFile);
|
|
}
|
|
|
|
if (rc == 0) {
|
|
printCSVHeader(csvFile);
|
|
|
|
for (int mcu = 0; mcu < mcuCount; mcu++) {
|
|
const char *mcuName = &nameTable[infoTable[mcu].nameAddr - baseAddr];
|
|
|
|
if (strncmp(mcuName, "STC12C54", 8) == 0 || strncmp(mcuName, "STC12LE54", 9) == 0) {
|
|
// STC12x54xx always have 12KB EEPROM
|
|
infoTable[mcu].eepromSize = 12 * 1024;
|
|
}
|
|
|
|
printCSVRow(csvFile, &infoTable[mcu], mcuName);
|
|
printMCUModel(&infoTable[mcu], mcuName);
|
|
}
|
|
|
|
free(infoTable);
|
|
free(nameTable);
|
|
}
|
|
|
|
if (csvFile != NULL) {
|
|
fclose(csvFile);
|
|
}
|
|
|
|
return rc;
|
|
}
|