Làm cách nào để tôi đọc từ / Proc / $ pid / mem trong Linux?


142

Các Linux proc(5)trang người đàn ông nói với tôi rằng /proc/$pid/mem“có thể được sử dụng để truy cập vào các trang của bộ nhớ của một quá trình”. Nhưng một nỗ lực đơn giản để sử dụng nó chỉ mang lại cho tôi

$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error

Tại sao không catthể in bộ nhớ của chính nó ( /proc/self/mem)? Và điều lạ lùng này là gì không có lỗi xử lý như vậy khi tôi cố gắng in bộ nhớ của vỏ ( /proc/$$/memrõ ràng là quá trình tồn tại)? Làm thế nào tôi có thể đọc từ /proc/$pid/mem, sau đó?


1
Có một số phương pháp khác cho thấy cách thực hiện điều này trên SF trong Hỏi & Đáp này có tiêu đề: Kết xuất bộ nhớ của quy trình linux vào tệp
slm

cập nhật câu trả lời
pizdelect

Câu trả lời:


140

/proc/$pid/maps

/proc/$pid/memhiển thị nội dung trong bộ nhớ của $ pid được ánh xạ giống như trong quy trình, tức là, byte tại offset x trong tệp giả giống như byte tại địa chỉ x trong quy trình. Nếu một địa chỉ không được ánh xạ trong quá trình, đọc từ phần bù tương ứng trong tệp trả về EIO(Lỗi đầu vào / đầu ra). Ví dụ, do trang đầu tiên trong một quy trình không bao giờ được ánh xạ (do đó, việc hủy bỏ một NULLcon trỏ không hoàn toàn thay vì truy cập bộ nhớ thực tế), đọc byte đầu tiên /proc/$pid/memluôn luôn gây ra lỗi I / O.

Cách để tìm ra phần nào của bộ nhớ quá trình được ánh xạ là đọc /proc/$pid/maps. Tệp này chứa một dòng trên mỗi vùng được ánh xạ, trông như thế này:

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0          [heap]

Hai số đầu tiên là ranh giới của vùng (địa chỉ của byte đầu tiên và byte sau cuối, trong hexa). Cột tiếp theo chứa các quyền, sau đó có một số thông tin về tệp (offset, thiết bị, inode và tên) nếu đây là ánh xạ tệp. Xem proc(5)trang hướng dẫn hoặc Hiểu Linux / Proc / id / maps để biết thêm thông tin.

Đây là một kịch bản bằng chứng khái niệm loại bỏ nội dung của bộ nhớ của chính nó.

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'r', 0)
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        print chunk,  # dump contents to standard output
maps_file.close()
mem_file.close()

/proc/$pid/mem

Nếu bạn cố đọc từ memtệp giả của quy trình khác, nó không hoạt động: bạn gặp lỗi ESRCH(Không có quy trình như vậy).

Các quyền trên /proc/$pid/mem( r--------) tự do hơn so với trường hợp nên có. Ví dụ: bạn không thể đọc bộ nhớ của quy trình setuid. Hơn nữa, cố gắng đọc bộ nhớ của một tiến trình trong khi quá trình đang sửa đổi nó có thể cung cấp cho người đọc một cái nhìn không nhất quán về bộ nhớ, và tệ hơn nữa, có những điều kiện chủng tộc có thể theo dõi các phiên bản cũ hơn của nhân Linux (theo chủ đề lkml này , mặc dù tôi không biết chi tiết). Vì vậy, kiểm tra bổ sung là cần thiết:

  • Quá trình muốn đọc từ /proc/$pid/memphải đính kèm với quy trình sử dụng ptracevới PTRACE_ATTACHcờ. Đây là những gì trình gỡ lỗi làm khi họ bắt đầu gỡ lỗi một quy trình; đó cũng là những gì mà stracehệ thống của một quá trình gọi. Khi người đọc đã đọc xong /proc/$pid/mem, nó sẽ tách ra bằng cách gọi ptracebằng PTRACE_DETACHcờ.
  • Quá trình quan sát không được chạy. Thông thường gọi ptrace(PTRACE_ATTACH, …)sẽ dừng quá trình đích (nó gửi STOPtín hiệu), nhưng có một điều kiện cuộc đua (phân phối tín hiệu không đồng bộ), vì vậy người theo dõi nên gọi wait(như được ghi trong tài liệu ptrace(2)).

Một tiến trình đang chạy như root có thể đọc bất kỳ bộ nhớ nào của tiến trình, mà không cần gọi ptrace, nhưng quá trình quan sát phải được dừng lại, hoặc đọc vẫn sẽ quay trở lại ESRCH.

Trong mã nguồn kernel Linux, mã cung cấp mục mỗi quá trình trong /proclà trong fs/proc/base.c, và các chức năng để đọc từ /proc/$pid/memmem_read. Việc kiểm tra bổ sung được thực hiện bởi check_mem_permission.

Dưới đây là một số mã C mẫu để đính kèm vào một quy trình và đọc một đoạn của memtệp (kiểm tra lỗi bị bỏ qua):

sprintf(mem_file_name, "/proc/%d/mem", pid);
mem_fd = open(mem_file_name, O_RDONLY);
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, NULL, 0);
lseek(mem_fd, offset, SEEK_SET);
read(mem_fd, buf, _SC_PAGE_SIZE);
ptrace(PTRACE_DETACH, pid, NULL, NULL);

Tôi đã đăng một kịch bản bằng chứng về khái niệm để bán phá giá /proc/$pid/memtrên một chủ đề khác .


2
@abc Không, đọc từ /proc/$pid/memtrực tiếp (cho dù với cathoặc ddhoặc bất cứ điều gì khác) không hoạt động. Đọc câu trả lời của tôi.
Gilles

4
@abc Anh ấy đọc từ /proc/self/mem. Một tiến trình có thể đọc không gian bộ nhớ của chính nó, nó đọc không gian bộ nhớ của một tiến trình khác yêu cầu PTRACE_ATTACH.
Gilles

2
Lưu ý rằng với các nhân Linux gần đây, bạn không cần PTRACE_ATTACH. Thay đổi này đi kèm với lệnh process_vm_readv()gọi hệ thống (Linux 3.2).
ysdx

2
Hừm, với Linux 4.14.8, điều này thực sự hiệu quả với tôi: bắt đầu một quá trình chạy dài đang bận viết đầu ra thành / dev / null. Sau đó, một quá trình khác có thể mở, tìm kiếm và đọc một số byte từ / Proc / $ otherpid / mem (tức là tại một số offset được tham chiếu qua vectơ phụ trợ) - mà không phải ptrace-Đính kèm / tách hoặc dừng / bắt đầu quá trình. Hoạt động nếu quá trình chạy dưới cùng một người dùng và cho người dùng root. Tức là tôi không thể mang lại một ESRCHlỗi trong kịch bản này.
maxschlepzig

1
@maxschlepzig Tôi đoán đó là sự thay đổi được đề cập bởi ysdx trong bình luận ở trên.
Gilles

28

Lệnh này (từ gdb) kết xuất bộ nhớ một cách đáng tin cậy:

gcore pid

Các bãi chứa có thể lớn, sử dụng -o outfilenếu thư mục hiện tại của bạn không có đủ chỗ.


12

Khi bạn thực hiện cat /proc/$$/membiến $$được đánh giá bằng bash sẽ chèn pid của chính nó. Sau đó, nó thực thi catcó một pid khác nhau. Bạn kết thúc với việc catcố gắng đọc bộ nhớ của bashquá trình cha mẹ của nó. Vì các tiến trình không có đặc quyền chỉ có thể đọc không gian bộ nhớ của riêng chúng, điều này bị hạt nhân từ chối.

Đây là một ví dụ:

$ echo $$
17823

Lưu ý rằng ước tính $$đến 17823. Hãy xem đó là quá trình nào.

$ ps -ef | awk '{if ($2 == "17823") print}'
bahamat  17823 17822  0 13:51 pts/0    00:00:00 -bash

Đó là cái vỏ hiện tại của tôi.

$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process

Ở đây một lần nữa $$đánh giá đến 17823, đó là vỏ của tôi. catkhông thể đọc không gian bộ nhớ của tôi.


Cuối cùng, bạn cố gắng đọc bộ nhớ của bất cứ điều gì $pid. Như tôi đã giải thích trong câu trả lời của mình, việc đọc bộ nhớ của một quá trình khác đòi hỏi bạn phải đọc nó.
Gilles

Mà sẽ được bash. Tôi đã không nói câu trả lời của bạn là sai. Tôi chỉ trả lời theo cách nói của giáo dân nhiều hơn "tại sao việc này không hiệu quả".
bahamat

@bahamat: Bạn có nghĩ đến $$khi bạn viết (và đọc) $pidkhông?
Gilles

Vâng ... anh ấy bắt đầu hỏi tham khảo $$và đặt $pidở cuối. Tôi chuyển nó trong đầu mà không nhận ra. Toàn bộ câu trả lời của tôi nên tham khảo $$, không $pid.
bahamat

@bahamat: Bây giờ câu hỏi có rõ ràng hơn không? (BTW Tôi không thấy bình luận của bạn trừ khi bạn sử dụng Hồi @Gilles, tôi chỉ tình cờ thấy bản chỉnh sửa của bạn và đến xem.)
Gilles

7

Đây là một chương trình nhỏ tôi đã viết trong C:

Sử dụng:

memdump <pid>
memdump <pid> <ip-address> <port>

Chương trình sử dụng / Proc / $ pid / maps để tìm tất cả các vùng bộ nhớ được ánh xạ của quá trình, sau đó đọc các vùng đó từ / Proc / $ pid / mem, mỗi lần một trang. các trang đó được ghi vào thiết bị xuất chuẩn hoặc địa chỉ IP và cổng TCP bạn đã chỉ định.

Mã (được thử nghiệm trên Android, yêu cầu quyền siêu người dùng):

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/socket.h>
#include <arpa/inet.h>

void dump_memory_region(FILE* pMemFile, unsigned long start_address, long length, int serverSocket)
{
    unsigned long address;
    int pageLength = 4096;
    unsigned char page[pageLength];
    fseeko(pMemFile, start_address, SEEK_SET);

    for (address=start_address; address < start_address + length; address += pageLength)
    {
        fread(&page, 1, pageLength, pMemFile);
        if (serverSocket == -1)
        {
            // write to stdout
            fwrite(&page, 1, pageLength, stdout);
        }
        else
        {
            send(serverSocket, &page, pageLength, 0);
        }
    }
}

int main(int argc, char **argv) {

    if (argc == 2 || argc == 4)
    {
        int pid = atoi(argv[1]);
        long ptraceResult = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
        if (ptraceResult < 0)
        {
            printf("Unable to attach to the pid specified\n");
            return;
        }
        wait(NULL);

        char mapsFilename[1024];
        sprintf(mapsFilename, "/proc/%s/maps", argv[1]);
        FILE* pMapsFile = fopen(mapsFilename, "r");
        char memFilename[1024];
        sprintf(memFilename, "/proc/%s/mem", argv[1]);
        FILE* pMemFile = fopen(memFilename, "r");
        int serverSocket = -1;
        if (argc == 4)
        {   
            unsigned int port;
            int count = sscanf(argv[3], "%d", &port);
            if (count == 0)
            {
                printf("Invalid port specified\n");
                return;
            }
            serverSocket = socket(AF_INET, SOCK_STREAM, 0);
            if (serverSocket == -1)
            {
                printf("Could not create socket\n");
                return;
            }
            struct sockaddr_in serverSocketAddress;
            serverSocketAddress.sin_addr.s_addr = inet_addr(argv[2]);
            serverSocketAddress.sin_family = AF_INET;
            serverSocketAddress.sin_port = htons(port);
            if (connect(serverSocket, (struct sockaddr *) &serverSocketAddress, sizeof(serverSocketAddress)) < 0)
            {
                printf("Could not connect to server\n");
                return;
            }
        }
        char line[256];
        while (fgets(line, 256, pMapsFile) != NULL)
        {
            unsigned long start_address;
            unsigned long end_address;
            sscanf(line, "%08lx-%08lx\n", &start_address, &end_address);
            dump_memory_region(pMemFile, start_address, end_address - start_address, serverSocket);
        }
        fclose(pMapsFile);
        fclose(pMemFile);
        if (serverSocket != -1)
        {
            close(serverSocket);
        }

        ptrace(PTRACE_CONT, pid, NULL, NULL);
        ptrace(PTRACE_DETACH, pid, NULL, NULL);
    }
    else
    {
        printf("%s <pid>\n", argv[0]);
        printf("%s <pid> <ip-address> <port>\n", argv[0]);
        exit(0);
    }
}

5
Thêm một số giải thích về mã của bạn. Nhận xét duy nhất của bạn là vô nghĩa: write to stdoutngay lập tức ở trên fwrite(..., stdout). Xem lập trình
viên.stackexchange.com/questions / 119600 / từ

Bạn nói rằng bạn chỉ thử nghiệm nó trên Android, vì vậy tôi chỉ muốn xác nhận, nó hoạt động tốt trên Linux 4.4.0-28 x86_64, như bạn mong đợi
cậu bé mai

tôi nhận được một loạt dữ liệu như / @ 8 l / @ l trên thiết bị xuất chuẩn không bao giờ kết thúc bất kỳ ý tưởng nào tại sao? được biên dịch trên Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05 / 02) x86_64 GNU / Linux Mô hình chủ đề: posix gcc phiên bản 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us

ceph3us, cách sử dụng phổ biến là dẫn dữ liệu vào một tệp (ví dụ: memdump <pid >> /sdcard/memdump.bin)
Tal Aloni
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.