Skip to content

File Types

mar edited this page May 15, 2023 · 1 revision

Embedding metadata is supported for hex and elf formats: this language implements a thin wrapper on the debug/elf go package, and an ihex parser to make it possible to write and read from and to a file with an API that gives random access capabilities to the user.

Table of Contents

File APIs

Files can be accessed in order to extract data from them and manipulating their contents.

When you access a file, its contents get loaded in memory and the os handle gets closed. You can manipulate the data and then choose to save the file onto the original one. You can interact with files with the following builtin functions:

  • open(filename: str, type: str) -> file_handle: open the file with the passed name, type can be hex, elf, or bytes.
  • save(file_handle): saves the changes to the file on the fs.
  • as_bytes(file_handle) -> []int: returns a copy of the file contents as an array of ints.

Hex file manipulation

Consider the following simple C program:

int main(void) {
  volatile int c = 0;
  c++;
  c *= 2;
  while(1);
}

And let's compile it for a generic avr mcu:

avr-gcc -O0 -o test.elf main.c
avr-objcopy -Oihex test.elf test.hex

We can then manipulate the hex file as follows:

>>> var h = try open("test.hex", "hex")
>>> print(h) // <- This prints the contents of the hex file as a string
:10000000CF93DF9300D0CDB7DEB71A8219828981F2
:100010009A8101969A83898389819A81880F991F91
:060020009A838983FFCFE3
:00000001FF

// Print information related to the hex file
>>> print("Number of records:", h.size()) 
Number of records: 4
>>> print("Actual data contained within the data records:", h.binary_size())
Actual data contained within the data records: 38

// Read n bytes (1st param) starting from position m (0th param)
>>> print(h.read_at(0, 16))
[207, 147, 223, 147, 0, 208, 205, 183, 222, 183, 26, 130, 25, 130, 137, 129]


// Write the array at the specified position
>>> try h.write_at(0, from_hex("deadbeef"))
>>> var first_word = try h.read_at(0, 16)
>>> print(first_word)
[222, 173, 190, 239, 0, 208, 205, 183, 222, 183, 26, 130, 25, 130, 137, 129]
>>> print(first_word, hex(first_word))
[222, 173, 190, 239, 0, 208, 205, 183, 222, 183, 26, 130, 25, 130, 137, 129] deadbeef00d0cdb7deb71a8219828981


// Print the contents of the hex file as a bytes array
>>> print(as_bytes(h))
[58, 49, 48, 48, 48, 48, 48, 48, 48, 100, 101, 97, 100, 98, 101, 101, 102, 48, 48, 68, 48, 67, 68, 66, 55, 68, 69, 66, 55, 49, 65, 56, 50, 49, 57, 56, 50, 56, 57, 56, 49, 56, 101, 13, 10, 58, 49, 48, 48, 48, 49, 48, 48, 48, 57, 65, 56, 49, 48, 49, 57, 54, 57, 65, 56, 51, 56, 57, 56, 51, 56, 57, 56, 49, 57, 65, 56, 49, 56, 56, 48, 70, 57, 57, 49, 70, 57, 49, 13, 10, 58, 48, 54, 48, 48, 50, 48, 48, 48, 57, 65, 56, 51, 56, 57, 56, 51, 70, 70, 67, 70, 69, 51, 13, 10, 58, 48, 48, 48, 48, 48, 48, 48, 49, 70, 70, 13, 10]

// Read the i-th record as a string
>>> print(try h.record(0))
:10000000DEADBEEF00D0CDB7DEB71A82198289818E

// Dump the written contents back to the file
>>> save(h)

Elf file manipulation

Consider the following simple c file:

static const unsigned char
data[256] __attribute__((section(".embmetadata")));

int main(void) {
  while(1) {}
}

And let's compile it for a generic avr mcu:

avr-gcc -O0 -o test.elf main.c

We can then manipulate the hex file as follows:

var e = try open("test.elf", "elf")
print(e) // <- This prints the elf object info
ElfFile(@test.elf) {
  Sections: [ .text .embmetadata .data .comment .symtab .strtab .shstrtab ]
}

// Check if the section with the given name exists
if e.has_section(".embmetadata") {
  print("'.embmetadata' section found in test.elf")
}
'.embmetadata' section found in test.elf

// This print will not get executed 
if e.has_section(".test") {
  print("'.test' section found in test.elf")
}

// Print each section
print(e.sections())
[, .text, .embmetadata, .data, .comment, .symtab, .strtab, .shstrtab]

// You can access the address of a section
print(".embetadata, addr:", hex(e.section_address(".embmetadata")))    
.embetadata, addr: 0x0a

// And its size
print(".embetadata, size: ", hex(e.section_size(".embmetadata")))
.embetadata, size:  0x10


// Read the contents of the given section and returns a copy as an array of ints
print(try e.read_section(".embmetadata"))
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]


// Write some data (an array of ints here) into the given section at offset 0
try e.write_section(".embmetadata", from_hex("cafe0102"), 0)    

print(try e.read_section(".embmetadata"))
[202, 254, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

print(hex(try e.read_section(".embmetadata")))
cafe0102000000000000000000000000


// Write some data (an array of ints here) into the given section at a non-zero offset
try e.write_section(".embmetadata", from_hex("beef0405"), 6) 

print(try e.read_section(".embmetadata"))
[202, 254, 1, 2, 0, 0, 190, 239, 4, 5, 0, 0, 0, 0, 0, 0]

print(hex(try e.read_section(".embmetadata")))
cafe01020000beef0405000000000000

print(as_bytes(h)) // prints [127, 69, 76, 70, 1, 1, 1, 0, 0, 0, 0, 0, 0, ... ]

// Dump the written contents back to the file
save(e)

Generic file manipulation

A generic file can be manipulated as a stream of bytes:

// test is a text file containing the following contents:
// "hello world!"

var b = try open("test", "bytes")
print(b) // prints the bytes object 

// Read 5 bytes from the file at position 0
print(b.read_at(0, 5)) // prints [32, 119, 111, 114, 108]

// Write some data (an array of ints here) at position 0
try b.write_at(0, [1, 2, 3])
print(b.read_at(0, 5)) // prints [1, 2, 3, 114, 108]

// Dump the written contents back to the file
save(e)

Clone this wiki locally