Let’s look again at the first bytes of the file foo.o:
(poke) dump :size 64#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............ 00000010: 0100 f700 0102 0000 0000 0000 0000 0000 ................ 00000020: 0102 0000 0000 0000 9801 0000 0000 0000 ................ 00000030: 0000 0000 4000 0000 0000 4000 0800 0700 ..............
At this point we know how to use the ruler to localize specific bytes just by looking at the displayed data. If we wanted to operate on the values of some given bytes, we could look at the dump and type the values in the REPL. For example, if we wanted to add the values of the bytes at offsets 0x2 and 0x4, we could look at the dump and then type:
(poke) 0x4c + 0x02 0x4e
GNU poke supports many operators that take integers as arguments, to perform arithmetic, relational, logical and bit-wise operations on them (see Integers). Since bytes are no more (and no less) than little unsigned integers, we can use these operators to perform calculations on bytes.
For example, this is how we would calculate whether the highest bit in the second byte in foo.o is set:
(poke) 0x45 & 0x80 0
Note how booleans are encoded in Poke as integers, 0 meaning false, any other value meaning true.
Looking at the output of dump
and writing the desired byte
value in the prompt is cumbersome. Fortunately, there is a much more
convenient way to access the value of a byte, given its offset in the
file: it is called mapping a byte value. This operation is
implemented by a binary operator, called the map operator.
This is how it works. Assuming we were interested in the byte at
64
bytes from the beginning of the file, this is how we would
refer to it (or “map” it):
(poke) byte @ 64#B 37UB
This application of the map operator tells poke to map a byte at the
offset 64 bytes. It can be read as “byte at 64 bytes”. Note how
poke replies with the value 37UB. The suffix UB
means
“unsigned byte”, and is an indication for the user about the nature
of the preceding number: it is unsigned, and it occupies a byte when
stored.
As we can see in this example, poke uses decimal by default when
showing values in the REPL. We already noted how it is usually better
to work in hexadecimal when dealing with byte values. Fortunately, we
can change the numeration base used by poke when printing numbers,
using the .set obase
(“set output base”) dot-command as
this:
(poke) .set obase 16
After this, we can map the byte again, this time getting the result expressed in hexadecimal:
(poke) byte @ 64#B 0x25UB
Again, you may find it useful to add the .set obase 16
command
to your .pokerc file, if you want the customization to be
persistent between poke invocations.
Going back to the example of calculating whether the highest bit in the second byte in foo.o is set, this is how we would do it with a map:
(poke) (byte @ 2#B) & 0x80 0
Turns out the answer is no.
The map operator can also be used at the left side of an assignment operator:
(poke) byte @ 0x28#B = 0xff
Which reads “assign 0xff to the byte at offset 0x4a bytes”. Dumping again, we can verify that the byte actually changed:
76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............ 00000010: 0100 f700 0102 0000 0000 0000 0000 0000 ................ 00000020: 0102 0000 0000 0000 ff01 0000 0000 0000 ................ 00000030: 0000 0000 4000 0000 0000 4000 0800 0700 ..............
Does this mean that foo.o changed accordingly, in disk? The answer is yes. poke always commits changes immediately to the file being edited. This, that is an useful feature, can also be a bit tricky if you forget about it, leading to data corruption, so please be careful.
Incidentally, altering the byte at offset 0x28 most probably have caused foo.o to stop being a valid ELF file, but since we are just editing bytes (and not ELF structures) we actually don’t care much.