【问题标题】:Why does the syscall "process_vm_readv" sets errno to "success"?为什么系统调用“process_vm_readv”将 errno 设置为“成功”?
【发布时间】:2018-07-31 13:10:12
【问题描述】:

我正在尝试在 Python 3 中实现调试器。主要思想非常简单:用 ctypes 包装系统调用“process_vm_readv”,然后在其他进程上调用它。

我还创建了一个小的虚拟 C++ 程序供我使用此工具进行调试。以下是两者的来源:


调试器

#!/usr/bin/python3

import typing
import ctypes
import os

libc = ctypes.cdll.LoadLibrary("libc.so.6")

def _error_checker(result, function, arguments):
    if result == -1:
        errno = ctypes.get_errno()
        raise OSError(errno, os.strerror(errno))

class IOBuffer(ctypes.Structure): # iovec struct
    _fields_ = [("base", ctypes.c_void_p),
                ("size", ctypes.c_size_t)]

_read_process_memory = libc.process_vm_readv
_read_process_memory.restype = ctypes.c_ssize_t
_read_process_memory.errcheck = _error_checker
_read_process_memory.args = [ctypes.c_ulong, ctypes.POINTER(IOBuffer),
                            ctypes.c_ulong, ctypes.POINTER(IOBuffer),
                            ctypes.c_ulong, ctypes.c_ulong]

def read_process_memory(pid: int, base: int, size: int) -> typing.Tuple[int, bytes]:
    buffer = (ctypes.c_char * size)()
    local = IOBuffer(ctypes.addressof(buffer), size)
    remote = IOBuffer(base, size)
    return _read_process_memory(pid, local, 1, remote, 1, 0), buffer.raw

虚拟程序

#include <iostream>
#include <stdio.h>

using namespace std;

int main(void){
    int a = 99;
    int c;
    while((c = getchar()) != EOF)
        cout << "int a=" << a << ";\t&a=" << &a << endl;
    return 0;
}

我的问题在于,每当我用我的虚拟程序的 pid 调用“read_process_memory”时,它提供给我的内存地址和数字 4(int 的大小)作为参数 - 这应该可以工作 - 包装系统调用返回 -1(错误)。发生这种情况时,errcheck 会报告该操作的 errno,它始终最终为零。 “错误成功”。由于这个无用的错误消息,我不知道如何解决这个问题。你们有什么想法可以解决这个问题吗?

【问题讨论】:

    标签: python-3.x debugging system-calls ctypes


    【解决方案1】:

    由于这个无用的错误消息,我不知道如何解决此问题

    您可以总是通过strace找出内核返回的真实错误。这样的事情应该可以工作:

    strace -e process_vm_readv python test.py
    

    【讨论】:

      【解决方案2】:

      按照Employed Russian's answer 的建议,我运行调试工具时在命令行中添加了strace -e process_vm_readv。它给了我以下错误:

      process_vm_readv(3464,
                       [{iov_base=NULL, iov_len=0},
                        {iov_base=NULL, iov_len=0},
                        {iov_base=0x7f9d39c0c4f8, iov_len=140313255527400},
                        {iov_base=0xfffffffffffffffa, iov_len=4}],
                       4, 0x1, 140726046508276, 4)
      = -1 EINVAL (Invalid argument)
      

      在修改了代码之后,我通过将read_process_memory 函数更改为:

      def read_process_memory(pid: int, base: int, size: int) -> typing.Tuple[int, bytes]:
          buffer = (ctypes.c_char * size)()
          local = (IOBuffer * 1)()
          local[0].base, local[0].size = ctypes.addressof(buffer), size
          remote = (IOBuffer * 1)()
          remote[0].base, remote[0].size = base, size
          return _read_process_memory(pid, local, 1, remote, 1, 0), buffer.raw
      

      我现在唯一的问题是 strace 向我抛出以下错误:

      process_vm_readv(3464,
                       [{iov_base=0x7fedc6f64450, iov_len=4}], 1,
                       [{iov_base=0x7ffd560344f4, iov_len=4}], 1, 0)
      = -1 EPERM (Operation not permitted)
      

      对此,我以 root 身份运行它,解决了最后一个问题。


      编辑: 正如 Mark Tolonen 所建议的,不是在 read_process_memory 中声明两个大小为 1 的数组,而是可以使用 ctypes.byref 将两个结构传递给包装函数,如下所示:

      def read_process_memory(pid: int, base: int, size: int) -> typing.Tuple[int, bytes]:
          buffer = (ctypes.c_char * size)()
          local = IOBuffer(ctypes.addressof(buffer), size)
          remote = IOBuffer(base, size)
          return _read_process_memory(pid, ctypes.byref(local), 1, ctypes.byref(remote), 1, 0), buffer.raw
      

      【讨论】:

      • 您可以使用_read_process_memory(pid,ctypes.byref(local),1,ctypes.byref(remote),1,0) 尝试您的原始代码。您不必声明大小为 1 的 IOBuffer 数组。
      • 确实是这样。我会尽快编辑我的答案以反映您的建议。谢谢。
      猜你喜欢
      • 2019-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-05
      • 1970-01-01
      • 2018-11-10
      • 2014-09-28
      相关资源
      最近更新 更多