Linux altında / proc / $ pid / mem sayfasından nasıl okuyabilirim?


142

Linux proc(5)adam sayfası söylüyor /proc/$pid/mem“Bir sürecin bellek sayfalara erişmek için kullanılabilir”. Ama onu kullanmak için basit bir girişimi sadece bana verir

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

Neden catkendi hafızasını yazdıramıyor ( /proc/self/mem)? Ve kabuğun hafızasını yazdırmaya çalıştığımda bu tuhaf “böyle bir işlem yok” hatası nedir ( /proc/$$/memaçıkçası işlem var)? O zaman nasıl okuyabilirim /proc/$pid/mem?


1
Bu soru-cevap bölümünde SF'de bunun nasıl yapıldığını gösteren birkaç yöntem daha var: Bir linux işleminin hafızasını dosyaya
slm

Yanıtlar:


140

/proc/$pid/maps

/proc/$pid/mem$ pid hafızasının içeriğini işlemdekiyle aynı şekilde gösterir, yani sözde dosyadaki x ofsetindeki bayt , işlemdeki x adresindeki bayt ile aynıdır . İşlem sırasında bir adres eşleşmezse, dosyadaki karşılık gelen uzaklıktan okuma döndürür EIO(Giriş / çıkış hatası). Örneğin, bir işlemdeki ilk sayfa hiçbir zaman eşleştirilmediğinden (bir NULLişaretçinin işaretinin kaldırılması, gerçek belleğe istemeden erişmeden ziyade temiz bir şekilde başarısız olması nedeniyle), ilk baytı okumak /proc/$pid/memher zaman bir G / Ç hatası verir.

İşlem belleğinin hangi kısımlarının haritalandığını bulmanın yolu okumaktır /proc/$pid/maps. Bu dosya, eşlenmiş bölge başına bir satır içerir ve şöyle görünür:

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

İlk iki sayı, bölgenin sınırlarıdır (ilk bayt ve sondan sonraki bayt adresleri, heksa cinsinden). Bir sonraki sütun izinleri içeriyorsa, bu dosya eşlemesiyse dosya hakkında (bilgi, ofset, aygıt, inode ve name) bazı bilgiler vardır. Daha fazla bilgi için proc(5)man sayfasına veya Linux / proc / id / maps'ü anlama bölümüne bakın .

İşte kendi hafızasının içeriğini harcayan bir konsept kanıtı betiği.

#! /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

memBaşka bir işlemin sözde dosyasından okumaya çalışırsanız , işe yaramaz: ESRCH(Böyle bir işlem yok) hatası alırsınız .

/proc/$pid/mem( r--------) Üzerindeki izinler, olması gerekenden daha liberaldir. Örneğin, setuid bir işlemin hafızasını okuyamamalısınız. Ayrıca, işlem değiştirilirken bir işlemin belleğini okumaya çalışmak, okuyucuya belleğin tutarsız bir görüntüsünü verebilir ve daha da kötüsü, Linux çekirdeğinin eski sürümlerini izleyebilecek yarış koşulları vardı (buna rağmen bu lkml dizisine göre). ayrıntıları bilmiyorum). Bu yüzden ek kontroller gerekli:

  • Dan okumak istiyor süreç /proc/$pid/memkullanarak işleme ekleme gerekir ptraceile PTRACE_ATTACHbayrak. Bu, hata ayıklayıcıların bir süreçte hata ayıklamaya başladığında yaptığı şeydir; aynı zamanda stracebir sürecin sisteminin çağırdığı şeydir . Okuyucu, okumayı bitirdikten sonra bayrak ile /proc/$pid/memarayarak ayrılmalıdır .ptracePTRACE_DETACH
  • Gözlenen işlem çalışmamalıdır. Normalde çağrı yapmak ptrace(PTRACE_ATTACH, …)hedef işlemi durduracaktır (bir STOPsinyal gönderir ), ancak bir yarış durumu vardır (sinyal teslimi asenkrondur), bu nedenle izleyici çağırmalıdır wait(belgelendiği gibi ptrace(2)).

Kök olarak çalışan bir işlem, çağrı yapmak zorunda kalmadan herhangi bir işlemin hafızasını okuyabilir ptrace, ancak gözlemlenen işlem durdurulmalı, yoksa okuma yine geri dönecektir ESRCH.

Linux çekirdek kaynak, kod içinde her işlem girişlerini sağlayan /prociçindedir fs/proc/base.cve okuma işlevi /proc/$pid/memolduğunu mem_read. Ek kontrol tarafından yapılır check_mem_permission.

İşte bir sürece eklemek ve memdosya için bir yığın okumak için bazı örnek C kodu (hata denetimi atlandı):

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

Ben zaten /proc/$pid/membaşka bir iş parçacığına dökmek için bir kavram kanıtı betiği yayınladım .


2
Hayır @abc, okuma /proc/$pid/memdoğrudan (ile olsun catveya ddçalışmıyor veya başka bir şey). Cevabımı oku.
Gilles

4
@ abc O okuyor /proc/self/mem. Bir işlem kendi hafıza alanını gayet iyi okuyabilir, başka bir işlemin gerektirdiği hafıza alanını okuyabilir PTRACE_ATTACH.
Gilles

2
Son Linux çekirdeğinde PTRACE_ATTACH'a ihtiyacınız olmadığını unutmayın. Bu değişiklik process_vm_readv()sistem çağrısı ile gelir (Linux 3.2).
ysdx

2
Hm, Linux 4.14.8 ile bu benim için işe yarar: / dev / null'a çıktı yazmakla meşgul uzun süren bir işlem başlatır. Daha sonra başka bir işlem / proc / $ otherpid / mem'den (yani yardımcı vektör üzerinden referans verilen bazı ofsetlerde) bazı baytları açabilir, arayabilir ve okuyabilir - işlemi başlatmak, kesmek / durdurmak veya durdurmak / başlatmak zorunda kalmadan. İşlem aynı kullanıcı altında ve kök kullanıcı için çalışıyorsa çalışır. Yani ESRCHbu senaryoda bir hata veremem .
maxschlepzig

1
@ maxschlepzig Yukarıdaki açıklamada ysdx tarafından belirtilen değişiklik bu galiba.
Gilles

28

Bu komut (gdb'den) belleği güvenilir bir şekilde doldurur:

gcore pid

Dökümler büyük -o outfileolabilir, geçerli dizininizde yer yoksa , kullanın .


12

Çalıştırdığınızda cat /proc/$$/memdeğişken $$kendi pidini ekleyen bash ile değerlendirilir. Daha sonra catfarklı bir pidine sahip olan yürütücü Sen ile bitirmek cathatırasını okumaya çalışıyorum bashüst süreci. Ayrıcalıklı olmayan süreçler sadece kendi hafıza alanlarını okuyabildiğinden, bu durum çekirdek tarafından reddedilir.

İşte bir örnek:

$ echo $$
17823

Bunun $$17823 olarak değerlendirildiğine dikkat edin. Hangi sürecin olduğunu görelim.

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

Bu benim şu anki kabuğum.

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

Burada yine $$, benim kabuğum olan 17823'ü değerlendiriyor. catkabuğumun hafıza alanını okuyamıyorum.


Sonunda ne $pidolduğunu hatırlamaya çalışarak bitiriyorsun . Cevabımda açıkladığım gibi, farklı bir sürecin hafızasını okumak onu zorla incelemenizi gerektirir.
Gilles

Hangi bash olacak. Cevabın yanlış olduğunu söylemedim. Sadece daha fazla meslekten olmayan kişinin "neden bu işe yaramazsa" terimlerini yanıtlıyordum.
bahamat

@ bahamat: Yazarken $$(ve okurken) $pidmi düşünüyorsunuz ?
Gilles

Evet ... o atıfta bulunarak başladı ve sonunda $$koydu $pid. Farkında olmadan kafamın içine aktardım. Benim tüm cevap başvurmalıdır $$değil $pid.
bahamat

@ bahamat: Soru şimdi net mi? (BTW “@Gilles” kullanmadığınız sürece yorumlarınızı göremiyorum, düzenlemenizi görmeye başladım ve görmeye geldim.)
Gilles

7

İşte C de yazdığım küçük bir program:

Kullanımı:

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

Program, işlemin tüm eşlenen bellek bölgelerini bulmak için / proc / $ pid / maps'i kullanır ve daha sonra bu bölgeleri bir kerede / proc / $ pid / mem sayfasından okur. Bu sayfalar stdout'a veya belirttiğiniz IP adresine ve TCP portuna yazılır.

Kod (Android'de test edilmiştir, süper kullanıcı izinleri gerektirir):

#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
Kodunuz hakkında bir açıklama ekleyin. Tek yorumunuz biraz anlamsız: write to stdouthemen yukarıda fwrite(..., stdout). Bkz programmers.stackexchange.com/questions/119600/...
Muru

Sen Beklediğiniz gibi, Linux üzerinde de 4.4.0-28 x86_64 çalışır, sadece Android'de test yüzden sadece onaylamak istediğini söyledi
kayısı çocuğu

neden hiç bir fikre ulaşamayan stdout'taki / @ 8 l / @ getl gibi veri gruplarını alıyorum? Linux'ta derlendi 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 GNU / Linux Konu modeli: posix gcc sürüm 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us

ceph3us, yaygın kullanım, verileri bir dosyaya aktarmaktır (örneğin, memdump <pid>> /sdcard/memdump.bin)
Tal Aloni,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.