1# Copyright (c) 2014, Tomohiro Kusumi
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice, this
8#    list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright notice,
10#    this list of conditions and the following disclaimer in the documentation
11#    and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24import os
25
26from . import filebytes
27from . import fileobj
28from . import kernel
29from . import log
30from . import ptrace
31from . import setting
32from . import util
33
34enabled = setting.use_pid_path and ptrace.has_ptrace()
35
36class methods (object):
37    def get_string(self):
38        l = []
39        l.append("pid {0}".format(self.pid))
40        l.append("name " + self.name)
41        l.append("word size {0}".format(self.word))
42        return '\n'.join(l)
43
44    def init_vm(self):
45        self.word = ptrace.get_word_size()
46        assert self.word != -1
47        self.pid = kernel.path_to_pid(self.get_path())
48        self.name = kernel.get_pid_name(self.pid)
49        self.test_vm()
50
51        offset = self.get_mapping_offset()
52        length = self.get_mapping_length()
53        if not length:
54            length = util.get_address_space() # will fail
55            length -= offset
56        self.init_chunk(self.__load_buffer(offset, length))
57
58    def __load_buffer(self, offset, length):
59        beg, end = util.align_range(offset, offset + length, self.word)
60        buf = self.read_vm(beg, end - beg)
61        x = offset - beg
62        b = buf[x : x + length]
63        assert len(b) == length, len(b)
64        return b
65
66    def get_vm_alias(self):
67        return self.name
68
69    def get_address(self, x):
70        return self.get_mapping_offset() + x
71
72    def __wait(self):
73        pid, status = kernel.waitpid(self.pid, 0)
74        if setting.use_debug:
75            ret = kernel.parse_waitpid_result(status)
76            log.debug("Wait pid {0}: {1}".format(pid, ret))
77
78    def test_vm(self):
79        if not kernel.has_pid_access(self.pid):
80            raise fileobj.Error("Can not access pid {0}".format(self.pid))
81
82    def __attach_vm(self):
83        ret, err = ptrace.attach(self.pid)
84        if ret == ptrace.ERROR:
85            raise fileobj.Error("Failed to attach pid {0}: {1}".format(self.pid,
86                os.strerror(err)))
87        self.__wait()
88
89    def __detach_vm(self):
90        ret, err = ptrace.detach(self.pid)
91        if ret == ptrace.ERROR:
92            raise fileobj.Error("Failed to detach pid {0}: {1}".format(self.pid,
93                os.strerror(err)))
94
95    def __assert_vm(self, addr, size):
96        assert addr % self.word == 0, addr
97        assert size % self.word == 0, size
98
99    def read_vm(self, addr, size):
100        self.__assert_vm(addr, size)
101        self.__attach_vm()
102        try:
103            return self.__peek_vm(addr, size)
104        except Exception as e:
105            log.error("{0}, retrying".format(e))
106            return self.__peek_vm(addr, size)
107        finally:
108            self.__detach_vm()
109
110    def __peek_vm(self, addr, size):
111        l = []
112        while True:
113            ret, err = ptrace.peektext(self.pid, addr)
114            if ret == ptrace.ERROR:
115                raise fileobj.Error(
116                    "Failed to peek pid {0} at 0x{1:X}: {2}".format(self.pid,
117                        addr, os.strerror(err)))
118            l.append(ret)
119            addr += self.word
120            size -= self.word
121            if size <= 0:
122                break
123        buf = filebytes.join(l)
124        assert len(buf) % self.word == 0, len(buf)
125        return buf
126
127    def write_vm(self, addr, buf):
128        self.__assert_vm(addr, 0)
129        self.__attach_vm()
130        try:
131            return self.__poke_vm(addr, buf)
132        except Exception as e:
133            log.error("{0}, retrying".format(e))
134            return self.__poke_vm(addr, buf)
135        finally:
136            self.__detach_vm()
137
138    def __poke_vm(self, addr, buf):
139        ret = 0
140        for data in buf:
141            ret, err = ptrace.poketext(self.pid, addr, data)
142            if ret == ptrace.ERROR:
143                raise fileobj.Error(
144                    "Failed to poke pid {0} at 0x{1:X}: {2}".format(self.pid,
145                        addr, os.strerror(err)))
146            ret += 1
147            addr += self.word
148        return ret
149