I've been working in a program for my final assignment and I've found the following strange behaviour.
I've coded a tracer in order to be able to read/write memory from child processes. My intention is to read the currently executed instruction at a given point, then disassemble it in order to get some information about memory operands and such.
For testing purposes a simple HelloWorld written in C is used.
The code of the tracer I've written, albeit simplified in order to be more understandable, is this:
size_t tracer::readMem(ADDR_t offset, char *buff, size_t len) {
REQUIRE(_state != TRCS_UNINITIALISED);
if (_memsdescr < 0 || fcntl(_memsdescr, F_GETFL) < 0) {
_memsdescr = open(("/proc/" + to_string(_child_pid_t) + "/mem").c_str(), O_LARGEFILE);
if (_memsdescr < 0) {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.log ( SLVL_ERROR, "Process\' memory could not be "
" opened. \n");
PANIC;
} else {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.logfmt ( SLVL_DEBUG, "Opened process' memory. %lx bytes long\n",
lseek(_memsdescr, 0, SEEK_END));
}
}
ASSERT(offset <= lseek(_memsdescr, 0, SEEK_END));
int ret = pread(_memsdescr, buff, len, offset);
if (ret < 0) {
logmanager::getInstance ().emplaceBasicLogger ("tracer")
.logfmt( SLVL_ERROR, "Error reading from memory descriptor: %s\n", sys_errlist[errno]);
}
return ret;
}
The code that controls the exectution is the following. Basically all it does is to read 15 bytes chunks from /proc/mem. The address of those chunks is obtained by getting the value of the RIP (instruction pointer) after a call to ptrace(PTRACE_SINGLESTEP). This means all the memory I try to read should be mapped in the process' memory space.
trc.load (filename);
trc.launchProgram();
cout << " Started with pid " << trc.getChildPid() << endl << endl;
//memspacy::memory_map::printSystemProcVmap(trc.getChildPid());
//inj.prop_setTraceSyscalls (true);
while (trc.prop_getState () != memspacy::TRCS_STOPPED) {
//if (trc.isSyscall()){
// trc.showSyscall();
//}
//HERE IS WHERE THE DISASSEMBLY takes place
if (trc.readMem(trc.peekReg(a_RIP), inst_buff, MAX_ARCH_INST_LEN)
&& dec.disassemble()) {
dec.printFormatted();
}
trc.singleStep();
}
The HelloWorld should be comprised of several thousand instructions, but the output I get is this.
mov %rsp, %rdi
add %al, (%rax)
push %rdi
push %rsi
push %rsp
mov %edi, %ebx
in %dx, %al
xor %ecx, -0x3f(%rax)
invalid
It seems that after a couple instructions the read function stops getting any data al all. No error is thrown, the only problem is that reading the memory returns 0 bytes. This would mean that the EOF is reached as per the information in the read() manpage, but lseek() returns a size of 0xFFFFFFFFFFFF, so there should be no problem on that end. Aso, all the reads fall within the mapped regions since I'm using the program counter as the offset.
I can not really think of any thing other than page permissions, but they all have read permissions set, otherwise it wouldn't even execute. The process is properly Ptraced, and the execution runs just fine, with the expected behaviour, even the registers are the exactly the same as in the control test (a test used to check the original behaviour).
My current guess is that at some point it reaches the end of a mapped region and that renders the descriptor useless, hence that "invalid" instruction at the end, but even opening the file on each read the result does not change.
Here is the memory map and the read offset of the last valid read.
00400000-00401000 r-xp 00000000 08:06 7602542 /home/amontes/workspace/memspacy_build/assets/test/test
00600000-00602000 rw-p 00000000 08:06 7602542 /home/amontes/workspace/memspacy_build/assets/test/test
**7fe3eb602000-7fe3eb625000 r-xp 00000000 08:11 657171 /lib/x86_64-linux-gnu/ld-2.19.so**
7fe3eb824000-7fe3eb826000 rw-p 00022000 08:11 657171 /lib/x86_64-linux-gnu/ld-2.19.so
7fe3eb826000-7fe3eb827000 rw-p 00000000 00:00 0
7fff57783000-7fff577a4000 rw-p 00000000 00:00 0 [stack]
7fff577fe000-7fff57800000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Last valid offset 7fe3eb606a7c -> This shows the invalid instruction
First invalid offset 7fe3eb606a7d -> This returns EOF
Any help or any idea would be really appreciated. Thank you.
Well, I don't know if it was a bug caused by some update, or a very specific kernel version or whatever you want to call it. After a clean install of the OS, everything works correctly. I can get the instruction stream, and the the read function always returns data.
Before wiping the HD and prior to the installation, I tried ptrace(PTRACE_PEEKDATA) without luck. Now everything works as it should.
I don't really believe this question is going to help anybody, but sometimes a clean start is the way to go. As much as I hate to admit it, it's happened to me from time to time, not always related to coding software.