PageRenderTime 49ms CodeModel.GetById 13ms app.highlight 31ms RepoModel.GetById 2ms app.codeStats 0ms

/src/dfinstancelinux.cpp

https://code.google.com/p/dwarftherapist/
C++ | 411 lines | 301 code | 42 blank | 68 comment | 57 complexity | eb1e220f55407b8cf93c6d321f32531b MD5 | raw file
  1/*
  2Dwarf Therapist
  3Copyright (c) 2009 Trey Stout (chmod)
  4
  5Permission is hereby granted, free of charge, to any person obtaining a copy
  6of this software and associated documentation files (the "Software"), to deal
  7in the Software without restriction, including without limitation the rights
  8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9copies of the Software, and to permit persons to whom the Software is
 10furnished to do so, subject to the following conditions:
 11
 12The above copyright notice and this permission notice shall be included in
 13all copies or substantial portions of the Software.
 14
 15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21THE SOFTWARE.
 22*/
 23#include <QtGui>
 24#include <QtDebug>
 25#include <sys/ptrace.h>
 26#include <errno.h>
 27#include <wait.h>
 28
 29#include "dfinstance.h"
 30#include "dfinstancelinux.h"
 31#include "defines.h"
 32#include "dwarf.h"
 33#include "utils.h"
 34#include "gamedatareader.h"
 35#include "memorylayout.h"
 36#include "cp437codec.h"
 37#include "memorysegment.h"
 38#include "truncatingfilelogger.h"
 39
 40DFInstanceLinux::DFInstanceLinux(QObject* parent)
 41    : DFInstance(parent)
 42{
 43}
 44
 45DFInstanceLinux::~DFInstanceLinux() {
 46    if (m_attach_count > 0) {
 47        detach();
 48    }
 49}
 50
 51QVector<uint> DFInstanceLinux::enumerate_vector(const uint &addr) {
 52    QVector<uint> addrs;
 53    if (!addr)
 54        return addrs;
 55
 56    attach();
 57    VIRTADDR start = read_addr(addr);
 58    VIRTADDR end = read_addr(addr + 4);
 59    int bytes = end - start;
 60    int entries = bytes / 4;
 61    TRACE << "enumerating vector at" << hex << addr << "START" << start
 62        << "END" << end << "UNVERIFIED ENTRIES" << dec << entries;
 63    VIRTADDR tmp_addr = 0;
 64
 65    if (entries > 5000) {
 66        LOGW << "vector at" << hexify(addr) << "has over 5000 entries! (" <<
 67                entries << ")";
 68    }
 69
 70#ifdef _DEBUG
 71    if (m_layout->is_complete()) {
 72        Q_ASSERT_X(start > 0, "enumerate_vector", "start pointer must be larger than 0");
 73        Q_ASSERT_X(end > 0, "enumerate_vector", "End must be larger than start!");
 74        Q_ASSERT_X(start % 4 == 0, "enumerate_vector", "Start must be divisible by 4");
 75        Q_ASSERT_X(end % 4 == 0, "enumerate_vector", "End must be divisible by 4");
 76        Q_ASSERT_X(end >= start, "enumerate_vector", "End must be >= start!");
 77        Q_ASSERT_X((end - start) % 4 == 0, "enumerate_vector", "end - start must be divisible by 4");
 78    } else {
 79        // when testing it's usually pretty bad to find a vector with more
 80        // than 5000 entries... so throw
 81        Q_ASSERT_X(entries < 5000, "enumerate_vector", "more than 5000 entires");
 82    }
 83#endif
 84    QByteArray data(bytes, 0);
 85    int bytes_read = read_raw(start, bytes, data);
 86    if (bytes_read != bytes && m_layout->is_complete()) {
 87        LOGW << "Tried to read" << bytes << "bytes but only got"
 88                << bytes_read;
 89        detach();
 90        return addrs;
 91    }
 92    for(int i = 0; i < bytes; i += 4) {
 93        tmp_addr = decode_dword(data.mid(i, 4));
 94        if (m_layout->is_complete()) {
 95            if (is_valid_address(tmp_addr))
 96                addrs << tmp_addr;
 97        } else {
 98            addrs << tmp_addr;
 99        }
100    }
101    detach();
102    return addrs;
103}
104
105uint DFInstanceLinux::calculate_checksum() {
106    // ELF binaries don't seem to store a linker timestamp, so just MD5 the file.
107    uint md5 = 0; // we're going to throw away a lot of this checksum we just need 4bytes worth
108    QProcess *proc = new QProcess(this);
109    QStringList args;
110    args << "md5sum";
111    args << QString("/proc/%1/exe").arg(m_pid);
112    proc->start("/usr/bin/env", args);
113    if (proc->waitForReadyRead(3000)) {
114        QByteArray out = proc->readAll();
115        QString str_md5(out);
116        QStringList chunks = str_md5.split(" ");
117        str_md5 = chunks[0];
118        bool ok;
119        md5 = str_md5.mid(0, 8).toUInt(&ok,16); // use the first 8 bytes
120        TRACE << "GOT MD5:" << md5;
121    }
122    return md5;
123}
124
125
126QString DFInstanceLinux::read_string(const VIRTADDR &addr) {
127    VIRTADDR buffer_addr = read_addr(addr);
128    int upper_size = 256;
129    QByteArray buf(upper_size, 0);
130    read_raw(buffer_addr, upper_size, buf);
131
132    QString ret_val(buf);
133    CP437Codec *codec = new CP437Codec;
134    ret_val = codec->toUnicode(ret_val.toAscii());
135    return ret_val;
136}
137
138int DFInstanceLinux::write_string(const VIRTADDR &addr, const QString &str) {
139    Q_UNUSED(addr);
140    Q_UNUSED(str);
141    return 0;
142}
143
144int DFInstanceLinux::write_int(const VIRTADDR &addr, const int &val) {
145    return write_raw(addr, sizeof(int), (void*)&val);
146}
147
148bool DFInstanceLinux::attach() {
149    TRACE << "STARTING ATTACH" << m_attach_count;
150    if (is_attached()) {
151        m_attach_count++;
152        TRACE << "ALREADY ATTACHED SKIPPING..." << m_attach_count;
153        return true;
154    }
155
156    if (ptrace(PTRACE_ATTACH, m_pid, 0, 0) == -1) { // unable to attach
157        perror("ptrace attach");
158        LOGE << "Could not attach to PID" << m_pid;
159        return false;
160    }
161    int status;
162    while(true) {
163        TRACE << "waiting for proc to stop";
164        pid_t w = waitpid(m_pid, &status, 0);
165        if (w == -1) {
166            // child died
167            perror("wait inside attach()");
168            LOGE << "child died?";
169            exit(-1);
170        }
171        if (WIFSTOPPED(status)) {
172            break;
173        }
174        TRACE << "waitpid returned but child wasn't stopped, keep waiting...";
175    }
176    m_attach_count++;
177    TRACE << "FINISHED ATTACH" << m_attach_count;
178    return m_attach_count > 0;
179}
180
181bool DFInstanceLinux::detach() {
182    TRACE << "STARTING DETACH" << m_attach_count;
183    m_attach_count--;
184    if (m_attach_count > 0) {
185        TRACE << "NO NEED TO DETACH SKIPPING..." << m_attach_count;
186        return true;
187    }
188
189    ptrace(PTRACE_DETACH, m_pid, 0, 0);
190    TRACE << "FINISHED DETACH" << m_attach_count;
191    return m_attach_count > 0;
192}
193
194int DFInstanceLinux::read_raw(const VIRTADDR &addr, int bytes, QByteArray &buffer) {
195    // try to attach, will be ignored if we're already attached
196    attach();
197
198    // open the memory virtual file for this proc (can only read once
199    // attached and child is stopped
200    QFile mem_file(QString("/proc/%1/mem").arg(m_pid));
201    if (!mem_file.open(QIODevice::ReadOnly)) {
202        LOGE << "Unable to open" << mem_file.fileName();
203        detach();
204        return 0;
205    }
206    int bytes_read = 0; // tracks how much we've read of what was asked for
207    int step_size = 0x1000; // how many bytes to read each step
208    QByteArray chunk(step_size, 0); // our temporary memory container
209    buffer.fill(0, bytes); // zero our buffer
210    int steps = bytes / step_size;
211    if (bytes % step_size)
212        steps++;
213
214    for(VIRTADDR ptr = addr; ptr < addr+ bytes; ptr += step_size) {
215        if (ptr+step_size > addr + bytes)
216            step_size = addr + bytes - ptr;
217        mem_file.seek(ptr);
218        chunk = mem_file.read(step_size);
219        buffer.replace(bytes_read, chunk.size(), chunk);
220        bytes_read += chunk.size();
221    }
222    mem_file.close();
223    detach();
224    return bytes_read;
225}
226
227int DFInstanceLinux::write_raw(const VIRTADDR &addr, const int &bytes,
228                               void *buffer) {
229    // try to attach, will be ignored if we're already attached
230    attach();
231
232    /* Since most kernels won't let us write to /proc/<pid>/mem, we have to poke
233     * out data in n bytes at a time. Good thing we read way more than we write.
234     *
235     * On x86-64 systems, the size that POKEDATA writes is 8 bytes instead of
236     * 4, so we need to use the sizeof( long ) as our step size.
237     */
238
239    // TODO: Should probably have a global define of word size for the
240    // architecture being compiled on. For now, sizeof(long) is consistent
241    // on most (all?) linux systems so we'll keep this.
242    uint stepsize = sizeof( long );
243    uint bytes_written = 0; // keep track of how much we've written
244    uint steps = bytes / stepsize;
245    if (bytes % stepsize)
246        steps++;
247    LOGD << "WRITE_RAW: WILL WRITE" << bytes << "bytes over" << steps << "steps, with stepsize " << stepsize;
248
249    // we want to make sure that given the case where (bytes % stepsize != 0) we don't
250    // clobber data past where we meant to write. So we're first going to read
251    // the existing data as it is, and then write the changes over the existing
252    // data in the buffer first, then write the buffer with stepsize bytes at a time 
253    // to the process. This should ensure no clobbering of data.
254    QByteArray existing_data(steps * stepsize, 0);
255    read_raw(addr, (steps * stepsize), existing_data);
256    LOGD << "WRITE_RAW: EXISTING OLD DATA     " << existing_data.toHex();
257
258    // ok we have our insurance in place, now write our new junk to the buffer
259    memcpy(existing_data.data(), buffer, bytes);
260    QByteArray tmp2(existing_data);
261    LOGD << "WRITE_RAW: EXISTING WITH NEW DATA" << tmp2.toHex();
262
263    // ok, now our to be written data is in part or all of the exiting data buffer
264    long tmp_data;
265    for (uint i = 0; i < steps; ++i) {
266        int offset = i * stepsize;
267        // for each step write a single word to the child
268        memcpy(&tmp_data, existing_data.mid(offset, stepsize).data(), stepsize);
269        QByteArray tmp_data_str((char*)&tmp_data, stepsize);
270        LOGD << "WRITE_RAW:" << hex << addr + offset << "HEX" << tmp_data_str.toHex();
271        if (ptrace(PTRACE_POKEDATA, m_pid, addr + offset, tmp_data) != 0) {
272            perror("write word");
273            break;
274        } else {
275            bytes_written += stepsize;
276        }
277        long written = ptrace(PTRACE_PEEKDATA, m_pid, addr + offset, 0);
278        QByteArray foo((char*)&written, stepsize);
279        LOGD << "WRITE_RAW: WE APPEAR TO HAVE WRITTEN" << foo.toHex();
280    }
281    // attempt to detach, will be ignored if we're several layers into an attach chain
282    detach();
283    // tell the caller how many bytes we wrote
284    return bytes_written;
285}
286
287bool DFInstanceLinux::find_running_copy(bool connect_anyway) {
288    // find PID of DF
289    TRACE << "attempting to find running copy of DF by executable name";
290    QProcess *proc = new QProcess(this);
291    QStringList args;
292    args << "dwarfort.exe"; // 0.31.04 and earlier
293    args << "Dwarf_Fortress"; // 0.31.05+
294    proc->start("pidof", args);
295    proc->waitForFinished(1000);
296    if (proc->exitCode() == 0) { //found it
297        QByteArray out = proc->readAllStandardOutput();
298        QString str_pid(out);
299        m_pid = str_pid.toInt();
300        m_memory_file.setFileName(QString("/proc/%1/mem").arg(m_pid));
301        TRACE << "FOUND PID:" << m_pid;
302    } else {
303        QMessageBox::warning(0, tr("Warning"),
304            tr("Unable to locate a running copy of Dwarf "
305            "Fortress, are you sure it's running?"));
306        LOGW << "can't find running copy";
307        m_is_ok = false;
308        return m_is_ok;
309    }
310
311    map_virtual_memory();
312
313    //qDebug() << "LOWEST ADDR:" << hex << lowest_addr;
314
315
316    //DUMP LIST OF MEMORY RANGES
317    /*
318    QPair<uint, uint> tmp_pair;
319    foreach(tmp_pair, m_regions) {
320        LOGD << "RANGE start:" << hex << tmp_pair.first << "end:" << tmp_pair.second;
321    }*/
322
323    VIRTADDR m_base_addr = read_addr(m_lowest_address + 0x18);
324    LOGD << "base_addr:" << m_base_addr << "HEX" << hex << m_base_addr;
325    m_is_ok = m_base_addr > 0;
326
327    uint checksum = calculate_checksum();
328    LOGD << "DF's checksum is" << hexify(checksum);
329    if (m_is_ok) {
330        m_layout = get_memory_layout(hexify(checksum).toLower(), !connect_anyway);
331    }
332
333    //Get dwarf fortress directory
334    m_df_dir = QDir(QFileInfo(QString("/proc/%1/cwd").arg(m_pid)).symLinkTarget());
335    LOGI << "Dwarf fortress path:" << m_df_dir.absolutePath();
336
337    return m_is_ok || connect_anyway;
338}
339
340void DFInstanceLinux::map_virtual_memory() {
341    // destroy existing segments
342    foreach(MemorySegment *seg, m_regions) {
343        delete(seg);
344    }
345    m_regions.clear();
346
347    if (!m_is_ok)
348        return;
349
350    // scan the maps to populate known regions of memory
351    QFile f(QString("/proc/%1/maps").arg(m_pid));
352    if (!f.open(QIODevice::ReadOnly)) {
353        LOGE << "Unable to open" << f.fileName();
354        return;
355    }
356    TRACE << "opened" << f.fileName();
357    QByteArray line;
358    m_lowest_address = 0xFFFFFFFF;
359    m_highest_address = 0;
360    uint start_addr = 0;
361    uint end_addr = 0;
362    bool ok;
363
364    QRegExp rx("^([a-f\\d]+)-([a-f\\d]+)\\s([rwxsp-]{4})\\s+[\\d\\w]{8}\\s+[\\d\\w]{2}:[\\d\\w]{2}\\s+(\\d+)\\s*(.+)\\s*$");
365    do {
366        line = f.readLine();
367        // parse the first line to see find the base
368        if (rx.indexIn(line) != -1) {
369            //LOGD << "RANGE" << rx.cap(1) << "-" << rx.cap(2) << "PERMS" <<
370            //        rx.cap(3) << "INODE" << rx.cap(4) << "PATH" << rx.cap(5);
371            start_addr = rx.cap(1).toUInt(&ok, 16);
372            end_addr = rx.cap(2).toUInt(&ok, 16);
373            QString perms = rx.cap(3).trimmed();
374            int inode = rx.cap(4).toInt();
375            QString path = rx.cap(5).trimmed();
376
377            //LOGD << "RANGE" << hex << start_addr << "-" << end_addr << perms
378            //        << inode << "PATH >" << path << "<";
379            bool keep_it = false;
380            if (path.contains("[heap]") || path.contains("[stack]") || path.contains("[vdso]"))  {
381                keep_it = true;
382            } else if (perms.contains("r") && inode && path == QFile::symLinkTarget(QString("/proc/%1/exe").arg(m_pid))) {
383                keep_it = true;
384            } else {
385                keep_it = path.isEmpty();
386            }
387            // uncomment to search HEAP only
388            //keep_it = path.contains("[heap]");
389            keep_it = true;
390
391            if (keep_it && end_addr > start_addr) {
392                MemorySegment *segment = new MemorySegment(path, start_addr, end_addr);
393                TRACE << "keeping" << segment->to_string();
394                m_regions << segment;
395                if (start_addr < m_lowest_address)
396                    m_lowest_address = start_addr;
397                else if (end_addr > m_highest_address)
398                    m_highest_address = end_addr;
399            }
400            if (path.contains("[heap]")) {
401                //LOGD << "setting heap start address at" << hex << start_addr;
402                m_heap_start_address = start_addr;
403            }
404        }
405    } while (!line.isEmpty());
406    f.close();
407}
408
409bool DFInstance::authorize() {
410    return true;
411}