Editing data with GNU poke mainly involves creating mapped values and
storing them in Poke variables. For example, if we were interested in
altering the fields of the header in an ELF file, we would map an
Elf64_Ehdr
struct at the beginning of the underlying IO space
(the file), like in:
(poke) .file foo.o (poke) load elf (poke) var ehdr = Elf64_Ehdr @ 0#B
At this point the variable ehdr
holds an Elf64_Ehdr
structure, which is mapped. As such, altering any of the fields of
the struct will update the corresponding bytes in foo.o
. For
example:
(poke) ehdr.e_entry = 0#B
A Poke value has three mapping related attributes: whether it is mapped, the offset at which it is mapped in an IO space, and in which IO space. This information is accessible for both the user and Poke programs using the following attributes:
(poke) ehdr'mapped 1 (poke) ehdr'offset 0UL#b (poke) ehdr'ios 0
That’s it, ehdr
is mapped at offset zero byte in the IO space
0
, which corresponds to foo.o
:
(poke) .info ios Id Type Mode Bias Size Name * 0 FILE rw 0x00000000#B 0x000004c8#B ./foo.o
Now that we have the ELF header, we may use it to get access to the
ELF section header table in the file, that we will reference using
another variable shdr
:
(poke) var shdr = Elf64_Shdr[ehdr.e_shnum] @ ehdr.e_shoff (poke) shdr[1] Elf64_Shdr { sh_name=0x1bU#B, sh_type=0x1U, sh_flags=#<ALLOC,EXECINSTR>, sh_addr=0x0UL#B, sh_offset=0x40UL#B, sh_size=0xbUL#B, sh_link=0x0U, sh_info=0x0U, sh_addralign=0x1UL, sh_entsize=0x0UL#b }
Variables are convenient entities to manipulate in Poke. Let’s suppose that the file has a lot of sections and we want to do some transformation in every section. It is a time consuming operation, and we may forget which sections we have already processed and which not. We could create an empty array to hold the sections already processed:
(poke) var processed = Elf64_Shdr[] ()
And then, once we have processed some given section, add it to the array:
... edit shdr[23] ... (poke) processed += [shdr[23]]
Note how the array =processed= is not mapped, but the sections contained in it are mapped: Poke uses copy by shared value. So, after we spend the day carefully poking our ELF file, we can ask poke, are we done with all the sections in the file?
(poke) shdr'length == processed'length 1
Yes, we are. This can be made as sophisticated as desired. We could
easily write a function that saves the contents of processed
in
files, so we can continue hacking tomorrow, for example.
We can then concluding that using mapped variables to edit data
structures stored in IO spaces works well in common and simple cases
like the above: we make our ways mapping here and there, defining
variables to hold data that interests us, and it is easy to remember
that the variables ehdr
and shdr
are mapped, where are
they mapped, and that they are mapped in the file foo.o
.
However, GNU poke allows to edit more than one IO space
simultaneously. Let’s say we now want to poke the sections of another
ELF file: bar.o
. We would start by opening the file:
(poke) .file bar.o (poke) .info ios Id Type Mode Bias Size Name * 1 FILE rw 0x00000000#B 0x000004c8#B ./bar.o 0 FILE rw 0x00000000#B 0x000004c8#B ./foo.o
Now that bar.o
is the current IO space, we can map its header.
But now, what variable to use? We would rather not redefine
ehdr
, because that is already holding the header of
foo.o
. We could adapt our naming schema on the fly:
(poke) var foo_ehdr = ehdr (poke) var bar_ehdr = Elf64_Ehdr @ 0#B
But then we would need to do the same for the other variables too:
(poke) var foo_shdr = shdr (poke) var bar_shdr = Elf64_Shdr[bar_ehdr.e_shnum] @ bar_ehdr.e_shoff
However, we can easily see how this can degenerate quickly: what about
processed
, for example? In general, as the number of IO spaces
being edited increases it becomes more and more difficult to manage
our mapped variables, which are associated to each IO space.