- Wed Sep 24, 2014 6:26 am
#878
wififofum wrote:Memory map
0x3ff00000 = peripheral registers base
0x3ffc8000 = stack base
0x3ffe8000 - 0x3fffffff = dram
0x40000000 - 0x4000ffff = mask rom
0x40100000 - 0x40107fff = iram
0x40200000 = external flash base
Well, yes and no. First, let's take a look at the memories defined in the core's config from the leaked VM. Of course, since this core is configurable, those values may or may not reflect what is in the actual chip at hand:
Code: Select all# Local memory info for ISS:
# Get the count for each local memory type, and if
# the count is non-zero, return a list of parameters
# [ size, base_address, access_width, busy, dma, cbox, rcw, parity, ecc, enable_mask, enable_code ]
# for each instance of that memory type.
#
ISSUnifiedRAMCount = 0
ISSDataRAMCount = 2
ISSDataRAMInfo = [ 0x40000 0x3ffc0000 32 1 0 0 0 0 0 0 0 0x40000 0x3ff80000 32 1 0 0 0 0 0 0 0 ]
ISSDataROMCount = 1
ISSDataROMInfo = [ 0x40000 0x3ff40000 32 1 0 0 0 0 0 0 0 ]
ISSInstRAMCount = 2
ISSInstRAMInfo = [ 0x100000 0x40000000 32 1 0 0 0 0 0 0 0 0x100000 0x40100000 32 1 0 0 0 0 0 0 0 ]
ISSInstROMCount = 1
ISSInstROMInfo = [ 0x100000 0x40200000 32 1 0 0 0 0 0 0 0 ]
ISSDataPortCount = 1
ISSDataPortInfo = [ 0x40000 0x3ff00000 32 1 0 0 0 0 0 0 0 ]
So we have several segments in the address space used for various RAM/ROM areas. The question now is how all that maps to the flash, which is yet to be found out. Now, there are a few hints in the leaked linker scripts. First, i noticed that the linker script says:
Code: Select alldram0_0_seg : org = 0x3FFE8000, len = 0x14000
This defines a region with 80k. However, writing a simple test app it turns out that i can actually write&read from a 96k big segment starting at that address, that is, 0x18000 size. I think that the remaining 16k are probably used for stack & heap of the internal ROM functions, but i'm not sure about that.
Next segment is the iram, start at 0x40100000 with a size of 0x8000, which is 32k, and matches what my test-app could also write to.
Now the interresting part regarding the flash. The standard linker file says:
Code: Select allirom0_0_seg : org = 0x40240000, len = 0x32000
That is 200k in size. Also note that to upload that segment to the chip, the tools use 0x40000 as address, so i assume that the internal bootloader uses 0x40200000 as it's base. Another thing to note is that the addresses given to the uploader tool seem to be the addresses where it ends up in physical flash. For example, the assembled data chunk that goes to 0x00000 is really at 0x00000 in the flash, and the chunk that is usually uploaded to 0x40000 is really at that address in the flash.
Now lets look at what the other two linker files say about the irom0 segment. app1 says:
Code: Select allirom0_0_seg : org = 0x40211000, len = 0x2B000
And app2 says:
Code: Select allirom0_0_seg : org = 0x40251000, len = 0x2B000
So, going by that, the first _known_ address where code can be run from is 0x40211000, the last one is 0x4027B000-1. This is a 424k large region. Of course there are a few things that need to be figured out: Is the internal bootloader & OS able to start code from any of these addresses directly, or does is assume a small chunk of code always be present at 0x40240000? What else is in the flash in that address range, for example some config data or user-storable data? LIke, there is a esp_init_data_default.bin that is located at 0x7C000 and a blank.bin that goes at 0x7E000, so it's obvious that we can't use those addresses for random code.
Writing code that places a chunk of code at specific addresses is rather easy. Just add the required sections to the linker script, and place the propper attribute in front of a function. For example, there is tthe irom0_0_seg for the code from flash. In c_types.h we have:
Code: Select all#define ICACHE_FLASH_ATTR __attribute__((section(".irom0.text")))
If you look at the example code, you see things like:
Code: Select allvoid ICACHE_FLASH_ATTR blinky_init(void) { ... }
Which places the final code for that function into the address space given by the irom0_0_seg definition in the linker script. Once we found out what actual address space is available in the flash (avoiding regions that contain user- or other data), we can simply add them to the linker script, and then duplicate the section in the script that places such code, create our own attribute defines, and happily place code there.
BTW, if you want to fiddle around with reading random addresses in the address space, you could abuse the AT firmware as a base (basically drop all the AT related crap, we only want the UART printing routines). Create some functions to print hex-bytes and -words, for example:
Code: Select allconst char hextab[16] = { "0123456789ABCDEF" };
void printhex(unsigned char c)
{
uart0_sendStr(" 0x");
uart_tx_one_char(UART0, hextab[ ((c & 0xF0 ) >> 4) ]);
uart_tx_one_char(UART0, hextab[ (c & 0x0F ) ] );
}
void print_hexaddr(unsigned long addr)
{
uart0_sendStr(" 0x");
uart_tx_one_char(UART0, hextab[ ((addr & 0xF0000000 ) >> 28) ]);
uart_tx_one_char(UART0, hextab[ ((addr & 0x0F000000 ) >> 24) ]);
uart_tx_one_char(UART0, hextab[ ((addr & 0x00F00000 ) >> 20) ]);
uart_tx_one_char(UART0, hextab[ ((addr & 0x000F0000 ) >> 16) ]);
uart_tx_one_char(UART0, hextab[ ((addr & 0x0000F000 ) >> 12) ]);
uart_tx_one_char(UART0, hextab[ ((addr & 0x00000F00 ) >> 8) ]);
uart_tx_one_char(UART0, hextab[ ((addr & 0xF00000F0 ) >> 4) ]);
uart_tx_one_char(UART0, hextab[ ( addr & 0x0000000F )] );
uart0_sendStr(" ");
}
Keep in mind that there is a crude, minimal OS running that handles a simple multitasking scheme. This also has a watchdog. You want to either disable it by first defining somewhere:
Code: Select allextern void ets_wdt_disable(void);
And then calling it in the main code. Alternatively you can define:
And then call that peridically to keep it from resetting the chip. (Those are two of the functions in the internal ROM).
Now, to access an address:
Code: Select allunsigned char *ramaddr;
ramaddr = 0;
ramaddr += 0x40100000;
print_hexaddr((unsigned long)ramaddr);
printhex(ramaddr[0]);
This will create a pointer into memory, called "ramaddr". Then you set it to 0, and then add to it the address you want. In this example i set the pointer address, then have it print the address, and then the byte at that address. You could also write:
Code: Select allunsigned char *ramaddr;
ramaddr = 0;
printhex(ramaddr[0x40100000]);
to print the byte at that address.
You can use the same pointer stuff to actually write to any address you want. However, it will reset as soon as you attempt to write to an address that you are not allowed to. To write somewhere:
Code: Select allunsigned char *ramaddr;
ramaddr = 0;
ramaddr += 0x40100000;
ramaddr[0] = 0xAB;
Keep in mind that if you work with pointers that way, if you add/substract from them, it is done in sizes that corrospond to the type. So if you have a char* pointer at 0x1000, and do a "mypointer += 1", it will end up at address 0x1001. If you have a unsigned long* pointer at 0x1000, and again do "mypointer += 1", it will end up at 0x1004.
Greetings,
Chris