#include "esptool.h"
#include "info.h"
#include "image.h"
#include "elffile.h"
#include "miniz.h"
#include "espcom.h"
#include <malloc.h> // _alloca, realloc
#include "miniz.h"
void Dumpfile::parseFrontOfName() {
const char*s=n,*q=strchr(s,':');
if (!q) return;
if (q==s) return; // At least one (digit) character before colon
// If the <fname> argument contains a ":", check whether number given is a candidate for hex address
char*e1,*e2;
addr=strtoul(s,&e1,0); // hexadecimal with 0x (to avoid disambiguity with "C:\user\...")
char c=*(e2=e1);
bool lenAvail = (c|0x20)=='l'; // "L" or "l" available
if (lenAvail) {
size=strtoul(e1+1,&e2,0);// After "L" or "l", there MUST be a number! Otherwise, it's invalid.
if (e2==e1+1) return; // No number: invalid
if (!size) return; // Number is zero: invalid
}
if (e2!=q) return; // Must point to the ':'
if (lenAvail) flags|=8;
if (e1!=s) flags|=4;
n=q+1; // true file name starts after ":" as prefix syntax is correct
}
uint32 Dumpfile::strlenp1(uint32 i) const{
if (i>=size) return 0; // don't scan, and don't add anymore
const byte*p=(const byte*)memchr(d+i,0,size-i);
if (p) return uint32(p-d)-i+1; // a terminating zero found
return size-i; // no zero found: assume end-of-buffer
}
Dumpfile::Dumpfile(byte chip_id):n(0),d(0),size(0),flags(0) {
const byte*img = esp::stub(chip_id); // load from resource
if (!img) return;
if (!ESPFirmwareImage::valid(img)) return; // resource error
d=const_cast<byte*>(img);
}
Dumpfile::Dumpfile(byte*d,uint32 s,uint32 a):n(0),d(d),size(s),addr(a),flags(3) {} // von Parser
// Memory dump files must either have an address in front of the real file name,
// or the hexadecimal address as part of the file name.
// flag bits input output
// flags.0: 0 address given by suffix
// flags.1: 0 length given by file size
// flags.2: 0 address given by prefix (has precedence over Bit 0)
// flags.3: 0 length given by prefix (has precedence over Bit 1)
// flags.4 0 file is gzip compressed: File pointer set to raw compressed data
// File is always open for reading (so switch to verify etc.)
Dumpfile::Dumpfile(const char*na):n(na),d(0),flags(0) {
parseFrontOfName();
FILE*f=fopen(n,"rb");
if (f) {
if (!(flags&8)) {
fseek(f,0,SEEK_END);
size=ftell(f);
fseek(f,0,SEEK_SET);
flags|=2;
}
d=new byte[size];
flags|=1<<7; // data owned!
if (fread(d,1,size,f)!=size) println(0x210,n); // "Error reading file %s"
fclose(f);
if (d[0]==0x1F && d[1]==0x8B) { // GZIP signature
byte flg=d[3];
uint32 i=10; // sizeof fixed GZIP header
if (flg&1<<2) i+=2+*(uint16*)(d+i); // skip FEXTRA
if (flg&1<<3) i+=strlenp1(i); // skip FNAME
if (flg&1<<4) i+=strlenp1(i); // skip FCOMMENT
if (flg&1<<1) i+=2; // skip (don't check) CRC16
if (i<size-8) {
d[0]=0x78; // make ZLIB header
d[1]=0xDA;
memmove(d+2,d+i,size-i-6); // kill GZIP header
memcpy(&ucrc,d+size-8,8); // get uncompressed CRC and size
size-=i+2; // reduce compressed data size by header and footer
// TODO: Decompress, only to generate Adler-32 value, but is it really needed by bootloader's inflate?
*reinterpret_cast<uint32*>(d+size-4)=0;
flags|=1<<4;
}else usize=size;
}else usize=size;
}
if (!(flags&4)) { // if not "address given", take address from file name
const char*s=n,
*pf=strrchr(s,'/'),
#ifdef WIN32
*pb=strrchr(s,'\\'), // Windows only
*pn=pf&&pb?pf>pb?pf+1:pb+1:pf?pf+1:pb?pb+1:s,
#else
*pn=pf?pf+1:s,
#endif
*pa=strrchr(pn,'@');
if (pa) {
char*pe;
addr = strtoul(++pa,&pe,16); // always hexadecimal without prefix
if (pe!=pa && (*pe=='.' || !*pe)) flags|=1;
}
}
}
Dumpfile::~Dumpfile() {if (flags&1<<7) {delete[] d; flags&=~(1<<7);}}
/* Opens file <fname>
Returns start address at <sadr> when given, and dissection occurs.
There are two options for placement (i.e. first address of binary file):
1. Valid hexadecimal address in front of ':' and real path name
2. Valid hexadecimal address after last '@' character in file name
So "bootloader@1000.bin", "partitions@8000.bin" and "main_program@10000.bin"
will automagically scatter to the right memory areas.
A load address given by address prefix (with ':') has precedence.
If placement info is given, <fname> is loaded as plain binary,
i.e. no segment iteration will be made.
With no placement information, one-layer iteration will be made.
This way, multiple ESP32-Image files may be placed into one cantainer-ESP32-Image!
*/
bool parseBinFile(const char*fname,cb2_t cb,void*cbd,uint32*sadr) {
Dumpfile df(fname);
if (!df.data()) {
println("Could not open file %s!",fname);
return false;
}
if (df.flags&8) {
println("Length argument not allowed here");
return false;
}
if (df.flags&1<<4) return cb(cbd,df);
if (ELFFile::valid(df.data(),df.size))
return ELFFile::parse(df,cb,cbd,sadr); // delegate
// UNCLEAR: Allowed to ôdecodeö firmware images for flash into partitions?
if (df.flags&5) { // address given
return cb(cbd,df); // BinΣrdatei: 1 äBlobô
}else{ // no address given
if (ESPFirmwareImage::valid(df.data(),df.size))
return ESPFirmwareImage::parse(df,cb,cbd,sadr); // Sonderfall äZwiebackô
println("Address necessary");
return false;
}
}
const char*param(const char*arg, const char*const*&argp) {
switch (*arg) {
case '=': return ++arg;
case 0: arg=*++argp; if (!arg) Fatal(0x102); return arg; // "Missing argument"
default: return arg;
// default: Fatal(0x013); // "Command line garbage"
}
}
// Find in string-pointer list, terminated by nullptr
int find(const char*needle, const char*const haystack[]) {
for (int i=0;*haystack;i++,haystack++) if (!strcmp(needle,*haystack)) return i;
return -1;
}
// Find in string list, concatenated by "\0", terminated by "\0\0"
int find(const char*needle, const char*haystack) {
for (int i=0;*haystack;i++,haystack+=strlen(haystack)+1) if (!strcmp(needle,haystack)) return i;
return -1;
}
void selftest() {
Sha256 sha256;
if (!sha256.selftest()) println("Fehler im %s-Algorithmus!","SHA-256");
Md5 md5;
if (!md5.selftest()) println("Fehler im %s-Algorithmus!","MD5");
const void* data=selftest;
const size_t dlen=3000;
size_t len;
void*packed = Deflate::mem_to_heap(data,dlen,len,0);
size_t elen;
void*extracted = Inflate::mem_to_heap(packed,len,elen,0);
if (elen!=dlen || memcmp(extracted,data,dlen)) println("Fehler im %s-Algorithmus!","Zip");
free(packed);
free(extracted);
}
int _cdecl main(int argc, char**argv) {
enum op{
none,
version,
image_info,
make_image,
elf2image,
merge_bin,
load_ram, // first op that needs serial connection to chip
read_mem,
write_mem,
dump_mem,
erase_region,
erase_flash,
write_flash,
read_flash,
verify_flash,
read_flash_status,
write_flash_status,
run,
read_mac,
flash_id,
chip_id,
get_security_info,
test,
}operation=none;
// Must be in sync with previous enum, beginning with first
static const char operations[]={
"version" "\0"
"image_info" "\0"
"make_image" "\0"
"elf2image" "\0"
"merge_bin" "\0"
"load_ram" "\0"
"read_mem" "\0"
"write_mem" "\0"
"dump_mem" "\0"
"erase_region" "\0"
"erase_flash" "\0"
"write_flash" "\0"
"read_flash" "\0"
"verify_flash" "\0"
"read_flash_status" "\0"
"write_flash_status" "\0"
"run" "\0"
"read_mac" "\0"
"flash_id" "\0"
"chip_id" "\0"
"get_security_info" "\0"
"test" "\0"
};
static const char*const fmode[]={"qio","qout","dio","dout",0};
static const char*const fsize[]={"1MB","2MB","4MB","8MB",0}; // Hi-Nibble 0,1,2,3
static const char*const ffreq[]={"40M","26M","20M","80M",0}; // Lo-Nibble 0,1,2,15
tty.init();
selftest();
ComSel comsel; // initialisiert sich per Defaultkonstruktor aus Registry!
struct Args{
char chip; // (82)66 oder 32
char flash_mode,flash_size,flash_freq;
unsigned entrypoint;
const char*input,*output;
}args;
memset(&args,0,sizeof args); // alles au▀er Comsel l÷schen
const char*const*argp;
for (argp=argv+1;;argp++) {
const char*arg=*argp;
if (!arg) break; // Ende der Kommandozeilenparameter
switch (arg[0]) {
case '-': switch (arg[1]) {
case 'p': comsel.port = (BYTE)strtoul(param(arg+2,argp)+3,0,0)-1; break; // "COM" stillschweigend ⁿbergehen
case 'b': comsel.baud = strtoul(param(arg+2,argp),0,0); break;
case 'c': {
const char*k=param(arg+2,argp);
const esp::Info1*info=esp::find1(k); // "ESP" ⁿbergehen wenn angegeben
if (!info) Fatal(0x104,k); // "Unbekannter Chip %s"
args.chip = info->image_chip_id;
}break;
case 'f': switch (arg[2]) {
case 'm': args.flash_mode = char(find(param(arg+3,argp),fmode)); break; // Flash-Modus
case 's': args.flash_size = char(find(param(arg+3,argp),fsize)); break; // Flash-Gr÷▀e
case 'f': args.flash_freq = char(find(param(arg+3,argp),ffreq)); break; // Flash-Frequenz
default: Fatal(0x105,arg); // "Unbekannte Option %s"
}break;
case 'i': args.input=param(arg+2,argp); break;
case 'o': args.output=param(arg+2,argp); break;
default: Fatal(0x105,arg); // "Unbekannte Option %s"
}break;
default: if (operation) goto endparse;
operation=op(find(arg,operations)+1);
if (!operation) Fatal(0x106,arg); // "Unbekannte Anweisung %s"
}
}
endparse:
/*
parser = argparse.ArgumentParser(description='ESP CHIP ROM Bootloader Utility', prog='esptool')
parser.add_argument("chip", 'c', help='CHIP_TYPE',choices=['ESP8266', 'ESP32'], default='ESP32')
parser.add_argument("port", 'p', help='Serial port device', default='COM3')
parser.add_argument("baud", 'b', help='Serial port baud rate',type=arg_auto_int,default=ESP::ROM_BAUD)
subparsers = parser.add_subparsers(dest='operation',help='Run esptool {command} -h for additional help')
parser_load_ram = subparsers.add_parser('load_ram',help='Download an image to RAM and execute')
parser_load_ram.add_argument("filename", help='Firmware image')
parser_dump_mem = subparsers.add_parser('dump_mem',help='Dump arbitrary memory to disk')
parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int)
parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int)
parser_dump_mem.add_argument('filename', help='Name of binary dump')
parser_read_mem = subparsers.add_parser('read_mem',help='Read arbitrary memory location')
parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int)
parser_write_mem = subparsers.add_parser('write_mem',help='Read-modify-write to arbitrary memory location')
parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int)
parser_write_mem.add_argument('value', help='Value', type=arg_auto_int)
parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int)
parser_write_flash = subparsers.add_parser('write_flash',help='Write a binary blob to flash')
parser_write_flash.add_argument('addr_filename', nargs='+', help='Address and binary file to write there, separated by space')
parser_write_flash.add_argument("flash_freq", '-ff', help='SPI Flash frequency',choices=['40m', '26m', '20m', '80m'], default='40m')
parser_write_flash.add_argument("flash_mode", '-fm', help='SPI Flash mode',choices=['qio', 'qout', 'dio', 'dout'], default='qio')
parser_write_flash.add_argument("flash_size", '-fs', help='SPI Flash size in Mbit',choices=['1MB', '2MB','4MB', '8MB', '16MB'], default='1MB')
subparsers.add_parser('run',help='Run application code in flash')
parser_image_info = subparsers.add_parser('image_info',help='Dump headers from an application image')
parser_image_info.add_argument('filename', help='Image file to parse')
parser_make_image = subparsers.add_parser('make_image',help='Create an application image from binary files')
parser_make_image.add_argument('output', help='Output image file')
parser_make_image.add_argument("segfile", 'f', action='append', help='Segment input file')
parser_make_image.add_argument("segaddr", 'a', action='append', help='Segment base address', type=arg_auto_int)
parser_make_image.add_argument("entrypoint", 'e', help='Address of entry point', type=arg_auto_int, default=0)
parser_elf2image = subparsers.add_parser('elf2image',help='Create an application image from ELF file')
parser_elf2image.add_argument('input', help='Input ELF file')
parser_elf2image.add_argument("output", 'o', help='Output filename prefix', type=str)
parser_elf2image.add_argument("flash_freq", '-ff', help='SPI Flash frequency',choices=['40m', '26m', '20m', '80m'], default='40m')
parser_elf2image.add_argument("flash_mode", '-fm', help='SPI Flash mode',choices=['qio', 'qout', 'dio', 'dout'], default='qio')
parser_elf2image.add_argument("flash_size", '-fs', help='SPI Flash size in Mbit',choices=['1MB', '2MB', '4MB', '8MB', '16MB'], default='1MB')
subparsers.add_parser('read_mac',help='Read MAC address from OTP ROM')
subparsers.add_parser('flash_id',help='Read SPI flash manufacturer and device ID')
parser_read_flash = subparsers.add_parser('read_flash',help='Read SPI flash content')
parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int)
parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int)
parser_read_flash.add_argument('filename', help='Name of binary dump')
subparsers.add_parser('erase_flash',help='Perform Chip Erase on SPI flash')
subparsers.add_parser('test')
args = parser.parse_args()
*/
ESP*esp = 0;
if (operation>=load_ram) { // Serial connection needed
for(;;) {
esp = new ESP(comsel.port);
if (esp->_port && esp->connect(comsel.baud)) break;
delete esp; esp=0;
if (!comsel.dialog()) Fatal(0x100); // "Abbruch durch Benutzer"
}
}
// Do the actual work.
switch (operation) {
case load_ram: {
if (!*argp) Fatal(0x107); // "Missing imgfilename"
printf("RAM load\n");
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
parseBinFile(arg,ESP::verboseDownloadCb,esp,&args.entrypoint);
}
println("All segments done, executing at %08x",args.entrypoint);
esp->mem_finish(args.entrypoint);
}break;
case read_mem: {
if (!*argp) Fatal(0x119); // "Missing list of addresses"
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
char*e;
uint32 addr=strtoul(arg,&e,0); // TODO: Detect malformed addresses; have a symbol list
if (*e) continue; // malformed
println("%#x => %#x",addr,esp->read_reg(addr));
}
}break;
case write_mem: {
if (!*argp) Fatal(0x11A); // "Missing list of addr:value[:mask]"
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
char*e;
uint32 addr=strtoul(arg,&e,0); // TODO: Detect malformed input
if (*e!=':') continue;
uint32 value=strtoul(e+1,&e,0),
mask=-1;
if (*e==':') mask=strtoul(e+1,&e,0);
if (*e) continue;
esp->write_reg(addr,value,mask,0);
println("%#x <= %#x, mask %#x",addr,value,mask);
}
}break;
case dump_mem: {
if (!*argp) Fatal(0x109); // "Missing list of [addr]Llen:binfilename[@hexaddr][.ext]"
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
Dumpfile df(arg);
if (df.data()) {
println("Won't overwrite file %s!",df.name());
df.~df();
continue;
}
if (df.flags&5 && df.flags&10) { // sowohl Adresse als auch LΣnge vorgefunden
FILE*f = fopen(df.name(),"wb");
if (!f) {
println("Cannot open/create %s for writing!",df.name());
continue;
}
for (uint32 i=0; i<df.size; i+=4) {
uint32 d = esp->read_reg(df.addr+i);
fwrite(&d,1,4,f);
if (!(i&1023)) {
printf("\r%d bytes read... (%d %%)",i,i*100/df.size);
fflush(stdout);
}
}
fclose(f);
}else println("Neither address nor length given at %s",arg);
}
println("Done!");
}break;
case write_flash: {
if (!*argp) Fatal(0x108); // "Missing list of [addr:]binfilename[@hexaddr][.ext]"
if (!esp->load_stub()) break;
// byte flash_info[2]={args.flash_mode,args.flash_size<<4|(args.flash_freq==3?15:args.flash_freq)};
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
parseBinFile(arg,ESP::write_flash_cb,esp,0);
}
/*
println("Leaving...");
if (args.flash_mode==2) esp->flash_unlock_dio(); // 'dio'
else {
esp->flash_begin(0,0,false);
esp->flash_finish(false,false);
}
*/
}break;
case run: esp->run(); break;
case image_info: {
if (!*argp) Fatal(0x107); // "Missing imgfilename"
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
printf("File %s:\n",arg);
ESPFirmwareImage image(args.chip,arg);
ESPFirmwareImage::IMGHDR&h=image.hdr;
printf("Flash mode: %s, Flash frequency: %s, Flash size: %s,\n",
image.fmode_str(),
image.ffreq_str().c_str(),
image.fsize_str().c_str());
// printf("Image version %u, ",image.version()); // TODO: Don't know where to get it
printf(h.entry?"Entry point: 0x%08X":"No entry point",h.entry);
printf(", %u segments:\n", h.nseg);
byte ck = esp::XOR8_INIT;
for (int i=0; i<image.hdr.nseg; i++) {
ESPFirmwareImage::SEG&cur=image.segments[i];
printf("\tSegment #%u:%7u bytes (0x%05X) to target 0x%08X %s", i, cur.size, cur.size, cur.offset,
esp::memareas(image.m_chip,cur.offset,cur.offset+cur.size).c_str());
putchar('\n');
ck = esp::xor8(cur.data, cur.size, ck);
}
if (image.check_avail) printf("Checksum: %02x (%s)\n", image.check, image.check == ck ? "valid" : "invalid!");
if (h.hash_append==1) {
bool equal=image.filehash==image.calchash;
printf("SHA-256 hash: %s (%s)\n",image.filehash.toString().c_str(),equal ? "valid" : "invalid!");
if (!equal) {
printf("file hash is: %s\n",image.calchash.toString().c_str());
} // Interessant! .c_str() ist nicht erforderlich, da kommt das gleiche 'raus
}
}
}break;
case make_image: {
if (!*argp) Fatal(0x108); // "Missing list of [addr:]binfilename[@hexaddr][.ext]"
if (!args.output) Fatal("No output filename given, write to stdout not supported (GetSaveFileName()?)");
ESPFirmwareImage image(args.chip);
// Leerzeichen erlaubt wenn entsprechend "escaped"
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
// TODO: Hier nicht zerlegen, einfach as-is laden und Zwieback akzeptieren
// TODO: ▄berlappungen ⁿberwachen (das darf nicht passieren)
parseBinFile(arg,ESPFirmwareImage::add_segment_cb,&image,&args.entrypoint);
}
image.hdr.entry = args.entrypoint;
image.save(args.output);
}break;
case elf2image: {
if (!args.output) {
size_t li = strlen(args.input);
char*p = new char[li+2];
memcpy(p,args.input,li);
p[li++]='-';
p[li]=0;
args.output = p;
}
ELFFile e(args.input);
ESPFirmwareImage image(args.chip);
image.hdr.entry = e.get_entry_point();
static const char*const sections[]={"text","data","rodata",0};
for (const char*const*sn=sections;*sn;sn++) {
char s[16];
_snprintf(s,sizeof s,".%s",*sn);
const void*data;
uint32 dlen = e.load_section(s,data);
_snprintf(s,sizeof s,"_%s_start",*sn);
Dumpfile sub((byte*)data,dlen,e.get_symbol_addr(s));
image.add_segment(sub);
delete[] const_cast<void*>(data);
}
image.hdr.fmode = args.flash_mode;
image.hdr.ffreq = args.flash_freq==3?15:args.flash_freq;
image.hdr.fsize = args.flash_size;
char s[256];
_snprintf(s,sizeof s,"%s@%X.bin",args.output,0);
image.save(s);
const void*data;
uint32 dlen = e.load_section(".irom0.text",data);
uint32 off = e.get_symbol_addr("_irom0_text_start") - 0x40200000;
// assert off >= 0
_snprintf(s,sizeof s,"%s@%X.bin",args.output,off);
FILE*f = fopen(s,"wb");
fwrite(data,1,dlen,f);
fclose(f);
delete[] const_cast<void*>(data);
}break;
case read_mac: { // macht gar nichts! Die MAC-Adresse wird immer angezeigt!
}break;
case flash_id: switch (esp->info1->image_chip_id) {
case 32: printf("SUPPORT ESP32 LATER"); break;
case 66: {
uint32 id = esp->flash_id();
printf("Manufacturer: %02x", byte(id));
printf("Device: %02x%02x",byte(id>>8),byte(id>>16));
}break;
}break;
case read_flash: {
if (!*argp) Fatal(0x109); // "Missing list of [addr]Llen:binfilename[@hexaddr][.ext]"
if (!esp->load_stub()) break;
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
if (!esp->flash2file(arg)) break;
}
}break;
case erase_flash: {
if (esp->load_stub()) esp->flash_erase();
}break;
case verify_flash: {
if (!*argp) Fatal(0x108); // "Missing list of [addr:]binfilename[@hexaddr][.ext]"
esp->load_stub(); // laut Datenblatt unn÷tig, aber es geht (bei mir) nicht
for (;;argp++) {
const char*arg=*argp;
if (!arg) break;
if (!esp->flash_verify(arg)) break;
}
}break;
case test: {
//esp->flash_begin(0x1000,0x200000)
byte res = esp->check_crc();
printf("res:",res);
}break;
case none: {
println(0x117); // "usage: esptool [-h] [-c ESPxxx] [-p COMx] [-b BAUD] action ..."
printf(0x118); // "actions: "
putchar('{'); ++tty.x;
for (const char*po=operations,*poe=po;*po;po=poe) {
poe=po+strlen(po)+1;
if (tty.x+poe-po+1>=tty.w-1) newline(); // Kein Platz fⁿr das nΣchste (Wort+Komma) auf der Zeile?
tty.x+=printf("%s%c",po,*poe?',':'}');
}
newline();
}break;
default: Fatal(0x101,*--argp); // not yet implemented
}
delete esp; // sonst kein Destruktoraufruf
return 0;
}
#ifdef _MSC_VER
extern "C" _CRTIMP int _cdecl __getmainargs(int&,char**&,char**&,int,int&);
int mainCRTStartup() {
int argc, newmode;
char**argv,**envp;
__getmainargs(argc,argv,envp,true,newmode);
ExitProcess(main(argc,argv));
}
#endif
Detected encoding: OEM (CP437) | 1
|
|