10

Context

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.

Information

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();
        }

Issue

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

Useful facts

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.

Data

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.

  • Run `ls -l /proc/*/mem`. Look at the file sizes. Read [this](http://unix.stackexchange.com/questions/6301/how-do-i-read-from-proc-pid-mem-under-linux). – n. m. could be an AI Aug 08 '14 at 12:28
  • Sorry but that seems not to be the problem. The file size was just a side argument; the addresses that RIP points to must be mapped into the process' memory as a part of the program's image prior to the dinamic linking and process creation. The size was just to point that getting a EOF shouldn't ever happen. Even reading at the end of the .code section should be possible taking into account the ELF format and mapping. The link does not completely relate to this issue and does not provide a solution to it. Thank you anyways :) –  Aug 08 '14 at 17:10
  • Sorry I don't understand the problem. `mem` is not a regular file and is not obliged to behave like one. You are trying to read it sequentially from the beginning. It won't work. You have to use it the way it's designed to be used, that is, the way outlined in the link. If you think the method outlined there doesn't let you do what you want to do, ask a question describing your problem with this method. – n. m. could be an AI Aug 08 '14 at 17:38
  • I've added some information and reestructured the question in order to clarify that point a bit. Btw, I think there wasn't, a single mention of sequential reads in the question. Instead, there was text in bold explaining I already use ptrace, that I don't get any errors, that I manage to extract some instructions until the issue manifests, and the code shows the use of rax as the offset of the reads. –  Aug 08 '14 at 19:08
  • OK I see the problem now. "it reaches the end of a mapped region and that renders the descriptor useless". Can you print out the offending offset and look it up in the /proc/pid/maps? You should never get EOF here, if you read past the end of the range you should get errno set to EIO. – n. m. could be an AI Aug 08 '14 at 20:05
  • Udated again with information about the memory map and RIP values. –  Aug 09 '14 at 09:36
  • Did the child process die, by chance? It looks similar to problems you sometimes see in GDB: like bad access at such and such address because the debugged program no longer exists. – Kaz Aug 15 '14 at 18:59
  • No, it never died during execution. It ended correctly every single time. The state is checked at each step and there are multiple conditions that ensure it is running. If I recall correctly, SIGABRT was the signal I got before those checks were in place. –  Aug 15 '14 at 22:28

1 Answers1

0

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.