2013-02-18 12 views
11

A causa di alcuni motivi oscuri che non sono rilevanti per questa domanda, ho bisogno di ricorrere a MAP_FIXED per ottenere una pagina vicino a dove la sezione di testo di libc risiede nella memoria.Pagine sovrapposte con mmap (MAP_FIXED)

Prima di leggere mmap (2) (che avrei dovuto fare in primo luogo), mi aspettavo di ottenere un errore se avessi chiamato mmap con MAP_FIXED e un indirizzo di base che si sovrapponesse a un'area già mappata.

Tuttavia, questo non è il caso. Per esempio, qui è parte di/proc/mappe per certo processo

7ffff7299000-7ffff744c000 r-xp 00000000 08:05 654098      /lib/x86_64-linux-gnu/libc-2.15.so 

Il che, dopo aver effettuato la seguente chiamata mmap ...

mmap(0x7ffff731b000, 
     getpagesize(), 
     PROT_READ | PROT_WRITE | PROT_EXEC, 
     MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 
     0, 
     0); 

... si trasforma in:

7ffff7299000-7ffff731b000 r-xp 00000000 08:05 654098      /lib/x86_64-linux-gnu/libc-2.15.so 
7ffff731b000-7ffff731c000 rwxp 00000000 00:00 0 
7ffff731c000-7ffff744c000 r-xp 00083000 08:05 654098      /lib/x86_64-linux-gnu/libc-2.15.so 

Ciò significa che ho sovrascritto una parte dello spazio degli indirizzi virtuali dedicato alla libc con la mia pagina personale. Chiaramente non quello che voglio ...

Nella parte MAP_FIXED della mmap (2) manuale, afferma chiaramente:

Se la regione di memoria specificata da addr e Len si sovrappone le pagine di qualsiasi mapping esistente (s), quindi la parte sovrapposta delle mappe esistenti sarà scartata.

Il che spiega quello che vedo, ma ho un paio di domande:

  1. C'è un modo per rilevare se qualcosa è stato già mappato certo indirizzo? senza accedere/proc/maps?
  2. C'è un modo per forzare mmap a fallire nel caso di trovare pagine sovrapposte?
+1

(+1) Questo può essere di qualche * * aiuto: http://stackoverflow.com/questions/8362747/how-can-i-detect-whether-a-specific-page-is -mapped-in-memory – NPE

+3

Penso che l'uso di "MAP_FIXED" sia una di quelle cose in cui "qui, hai questa pistola, ma fai attenzione a non spararti ai piedi". In altre parole, "è tuo compito assicurarti di usarlo correttamente". Ho esaminato parte del codice mmap in passato, e per quanto ne so, fa quello che chiedi, qualunque cosa tu chieda veramente - purché non stia violando la sicurezza [e la tua applicazione distrugga la sua copia di la libreria C non è una violazione della sicurezza, dal momento che tutto ciò che accadrà è che la TUA applicazione muore ...] –

+1

@MatsPetersson - abbastanza corretto, ma è anche possibile _possibile_ usarlo corretto? Ad esempio, se sei consapevole che 'MAP_FIXED' sovrascrive i mapping esistenti e sei disposto a _check_ per i mapping esistenti, c'è un modo ragionevole per farlo? – BeeOnRope

risposta

5
  1. Usa page = sysconf(SC_PAGE_SIZE) per scoprire la dimensione della pagina, quindi eseguire la scansione ogni blocco pagina di dimensioni che si desidera controllare utilizzando msync(addr, page, 0) (con (unsigned long)addr % page == 0, vale a dire addr allineato alle pagine). Se restituisce -1 con errno == ENOMEM, quella pagina non viene mappata.

    Modificato: come indicato sopra, il documento mincore(addr,page,&dummy) è superiore a msync(). (L'implementazione di syscall è in mm/mincore.c nei sorgenti del kernel Linux, con le librerie C che di solito forniscono un wrapper che aggiorna errno. Come syscall esegue il controllo di mappatura immediatamente dopo aver verificato che la pagina addr sia allineata alla pagina, è ottimale nel non mappato caso (ENOMEM). Funziona un po 'se la pagina è già mappata, quindi se le prestazioni sono fondamentali, prova a evitare di controllare le pagine che conosci mappate

    È necessario farlo individualmente, separatamente per ogni pagina, perché per le regioni più grande di una singola pagina, ENOMEM significa che la regione non è stata mappata completamente, ma potrebbe essere ancora parzialmente mappata. La mappatura è sempre granulare a unità di dimensioni di pagina.

  2. Per quanto posso dire, non c'è modo di dire mmap() fallire se la regione è già mappata, o contiene già pagine mappate. (Lo stesso vale per mremap(), quindi non è possibile creare una mappatura, quindi spostarla nella regione desiderata.)

    Ciò significa che si corre il rischio di una condizione di competizione. Sarebbe meglio per eseguire le chiamate di sistema effettivi da soli, invece dei wrapper della libreria C, nel caso in cui lo fanno allocazione di memoria o modificare le mappature di memoria interna:

    #define _GNU_SOURCE 
    #include <unistd.h> 
    #include <sys/syscall.h> 
    
    static size_t page = 0; 
    static inline size_t page_size(void) 
    { 
        if (!page) 
         page = (size_t)sysconf(_SC_PAGESIZE); 
        return page; 
    } 
    
    
    static inline int raw_msync(void *addr, size_t length, int flags) 
    { 
        return syscall(SYS_msync, addr, length, flags); 
    } 
    
    static inline void *raw_mmap(void *addr, size_t length, int prot, int flags) 
    { 
        return (void *)syscall(SYS_mmap, addr, length, prot, flags, -1, (off_t)0); 
    } 
    

Tuttavia, ho il sospetto che qualunque cosa tu stanno cercando di fare, alla fine è necessario analizzare lo /proc/self/maps in ogni caso.

  • Consiglio evitando standard di I/O stdio.h completamente (come le varie operazioni allocherà la memoria dinamica, e quindi modificare i mapping), e utilizza un livello inferiore unistd.h interfacce, che sono molto meno possono incidere le mappature. Ecco una serie di funzioni semplici e crude, che puoi utilizzare per scoprire ogni regione mappata e le protezioni attivate in quella regione (e scartare le altre informazioni). In pratica, usa circa un kilobyte di codice e meno di quello in stack, quindi è molto utile anche su architetture limitate (ad esempio, dispositivi embedded).

    #include <unistd.h> 
    #include <fcntl.h> 
    #include <errno.h> 
    #include <string.h> 
    
    #ifndef INPUT_BUFFER 
    #define INPUT_BUFFER 512 
    #endif /* INPUT_BUFFER */ 
    
    #ifndef INPUT_EOF 
    #define INPUT_EOF  -256 
    #endif /* INPUT_EOF */ 
    
    #define PERM_PRIVATE 16 
    #define PERM_SHARED 8 
    #define PERM_READ  4 
    #define PERM_WRITE  2 
    #define PERM_EXEC  1 
    
    typedef struct { 
        int   descriptor; 
        int   status; 
        unsigned char *next; 
        unsigned char *ends; 
        unsigned char buffer[INPUT_BUFFER + 16]; 
    } input_buffer; 
    
    /* Refill input buffer. Returns the number of new bytes. 
    * Sets status to ENODATA at EOF. 
    */ 
    static size_t input_refill(input_buffer *const input) 
    { 
        ssize_t n; 
    
        if (input->status) 
         return (size_t)0; 
    
        if (input->next > input->buffer) { 
         if (input->ends > input->next) { 
          memmove(input->buffer, input->next, 
            (size_t)(input->ends - input->next)); 
          input->ends = input->buffer + (size_t)(input->ends - input->next); 
          input->next = input->buffer; 
         } else { 
          input->ends = input->buffer; 
          input->next = input->buffer; 
         } 
        } 
    
        do { 
         n = read(input->descriptor, input->ends, 
           INPUT_BUFFER - (size_t)(input->ends - input->buffer)); 
        } while (n == (ssize_t)-1 && errno == EINTR); 
        if (n > (ssize_t)0) { 
         input->ends += n; 
         return (size_t)n; 
    
        } else 
        if (n == (ssize_t)0) { 
         input->status = ENODATA; 
         return (size_t)0; 
        } 
    
        if (n == (ssize_t)-1) 
         input->status = errno; 
        else 
         input->status = EIO; 
    
        return (size_t)0; 
    } 
    
    /* Low-lever getchar() equivalent. 
    */ 
    static inline int input_next(input_buffer *const input) 
    { 
        if (input->next < input->ends) 
         return *(input->next++); 
        else 
        if (input_refill(input) > 0) 
         return *(input->next++); 
        else 
         return INPUT_EOF; 
    } 
    
    /* Low-level ungetc() equivalent. 
    */ 
    static inline int input_back(input_buffer *const input, const int c) 
    { 
        if (c < 0 || c > 255) 
         return INPUT_EOF; 
        else 
        if (input->next > input->buffer) 
         return *(--input->next) = c; 
        else 
        if (input->ends >= input->buffer + sizeof input->buffer) 
         return INPUT_EOF; 
    
        memmove(input->next + 1, input->next, (size_t)(input->ends - input->next)); 
        input->ends++; 
        return *(input->next) = c; 
    } 
    
    /* Low-level fopen() equivalent. 
    */ 
    static int input_open(input_buffer *const input, const char *const filename) 
    { 
        if (!input) 
         return errno = EINVAL; 
    
        input->descriptor = -1; 
        input->status = 0; 
        input->next = input->buffer; 
        input->ends = input->buffer; 
    
        if (!filename || !*filename) 
         return errno = input->status = EINVAL; 
    
        do { 
         input->descriptor = open(filename, O_RDONLY | O_NOCTTY); 
        } while (input->descriptor == -1 && errno == EINTR); 
        if (input->descriptor == -1) 
         return input->status = errno; 
    
        return 0; 
    } 
    
    /* Low-level fclose() equivalent. 
    */ 
    static int input_close(input_buffer *const input) 
    { 
        int result; 
    
        if (!input) 
         return errno = EINVAL; 
    
        /* EOF is not an error; we use ENODATA for that. */ 
        if (input->status == ENODATA) 
         input->status = 0; 
    
        if (input->descriptor != -1) { 
         do { 
          result = close(input->descriptor); 
         } while (result == -1 && errno == EINTR); 
         if (result == -1 && !input->status) 
          input->status = errno; 
        } 
    
        input->descriptor = -1; 
        input->next = input->buffer; 
        input->ends = input->buffer; 
    
        return errno = input->status; 
    } 
    
    /* Read /proc/self/maps, and fill in the arrays corresponding to the fields. 
    * The function will return the number of mappings, even if not all are saved. 
    */ 
    size_t read_maps(size_t const n, 
           void **const ptr, size_t *const len, 
           unsigned char *const mode) 
    { 
        input_buffer input; 
        size_t   i = 0; 
        unsigned long curr_start, curr_end; 
        unsigned char curr_mode; 
        int    c; 
    
        errno = 0; 
    
        if (input_open(&input, "/proc/self/maps")) 
         return (size_t)0; /* errno already set. */ 
    
        c = input_next(&input); 
        while (c >= 0) { 
    
         /* Skip leading controls and whitespace */ 
         while (c >= 0 && c <= 32) 
          c = input_next(&input); 
    
         /* EOF? */ 
         if (c < 0) 
          break; 
    
         curr_start = 0UL; 
         curr_end = 0UL; 
         curr_mode = 0U; 
    
         /* Start of address range. */ 
         while (1) 
          if (c >= '0' && c <= '9') { 
           curr_start = (16UL * curr_start) + c - '0'; 
           c = input_next(&input); 
          } else 
          if (c >= 'A' && c <= 'F') { 
           curr_start = (16UL * curr_start) + c - 'A' + 10; 
           c = input_next(&input); 
          } else 
          if (c >= 'a' && c <= 'f') { 
           curr_start = (16UL * curr_start) + c - 'a' + 10; 
           c = input_next(&input); 
          } else 
           break; 
         if (c == '-') 
          c = input_next(&input); 
         else { 
          errno = EIO; 
          return (size_t)0; 
         } 
    
         /* End of address range. */ 
         while (1) 
          if (c >= '0' && c <= '9') { 
           curr_end = (16UL * curr_end) + c - '0'; 
           c = input_next(&input); 
          } else 
          if (c >= 'A' && c <= 'F') { 
           curr_end = (16UL * curr_end) + c - 'A' + 10; 
           c = input_next(&input); 
          } else 
          if (c >= 'a' && c <= 'f') { 
           curr_end = (16UL * curr_end) + c - 'a' + 10; 
           c = input_next(&input); 
          } else 
           break; 
         if (c == ' ') 
          c = input_next(&input); 
         else { 
          errno = EIO; 
          return (size_t)0; 
         } 
    
         /* Permissions. */ 
         while (1) 
          if (c == 'r') { 
           curr_mode |= PERM_READ; 
           c = input_next(&input); 
          } else 
          if (c == 'w') { 
           curr_mode |= PERM_WRITE; 
           c = input_next(&input); 
          } else 
          if (c == 'x') { 
           curr_mode |= PERM_EXEC; 
           c = input_next(&input); 
          } else 
          if (c == 's') { 
           curr_mode |= PERM_SHARED; 
           c = input_next(&input); 
          } else 
          if (c == 'p') { 
           curr_mode |= PERM_PRIVATE; 
           c = input_next(&input); 
          } else 
          if (c == '-') { 
           c = input_next(&input); 
          } else 
           break; 
         if (c == ' ') 
          c = input_next(&input); 
         else { 
          errno = EIO; 
          return (size_t)0; 
         } 
    
         /* Skip the rest of the line. */ 
         while (c >= 0 && c != '\n') 
          c = input_next(&input); 
    
         /* Add to arrays, if possible. */ 
         if (i < n) { 
          if (ptr) ptr[i] = (void *)curr_start; 
          if (len) len[i] = (size_t)(curr_end - curr_start); 
          if (mode) mode[i] = curr_mode; 
         } 
         i++; 
        } 
    
        if (input_close(&input)) 
         return (size_t)0; /* errno already set. */ 
    
        errno = 0; 
        return i; 
    } 
    

    La funzione read_maps() legge fino a n regioni, iniziare indirizzi come void * nell'array ptr, lunghezze nella matrice len e autorizzazioni nell'array mode, restituendo il numero totale di mappe (può essere maggiore di n) o zero con errno impostato se si verifica un errore.

    È abbastanza possibile utilizzare syscalls per l'I/O di livello inferiore sopra, in modo da non utilizzare alcuna funzionalità di libreria C, ma non penso che sia affatto necessario. (Le librerie C, per quanto posso dire, usano molto semplici wrapper per le chiamate di sistema effettivi per questi.)

Spero che hai trovato utile.

+0

Da dove hai preso read_maps? libproc? – fons

+0

Non sarebbe meglio usare le prestazioni mincore() piuttosto che msync() poiché non ha implicazioni dirette sull'IO? – fons

+0

@fons: 'read_maps()' è implementato alla fine del secondo snippet di origine. L'ho scritto da zero (era annoiato), contro i documenti del kernel.Queste sono interfacce stabili, quindi se procfs è montato su '/ proc', dovrebbe funzionare. Per quanto riguarda 'mincore()', assolutamente - ma di nuovo, solo se si controlla ogni singola pagina. (Ho appena controllato le ultime fonti del kernel per 'mincore()' syscall in 'mm/mincore.c', ed è praticamente ottimale: esegue' access_ok() 'immediatamente dopo aver verificato l'allineamento della pagina.) Quindi sì; mincore() sarebbe la scelta migliore. –

3

Sembra che posix_mem_offset() sia quello che stavo cercando.

Non solo indica se un indirizzo è mappato ma anche, nel caso in cui sia mappato, fornisce implicitamente i limiti dell'area mappata a cui appartiene (fornendo SIZE_MAX nell'argomento len).

Quindi, prima di imporre MAP_FIXED, è possibile utilizzare posix_mem_offset() per verificare che l'indirizzo che sto utilizzando non sia ancora mappato.

potevo usare msync() o mincore() troppo (il controllo di un errore ENOMEM che un indirizzo è già mappato dice), ma poi sarei cieca (senza informazioni sulla zona in cui è mappato l'indirizzo). Inoltre, msync() ha effetti collaterali che possono avere un impatto sulle prestazioni e mincore() è solo BSD (non POSIX).

+2

Gli attuali kernel di Linux non forniscono tale syscall, e almeno 'libc6-2.15-0ubuntu10.3' non fornisce una funzione 'posix_mem_offset()', quindi 'posix_mem_offset()' potrebbe non essere portabile come si pensa. –

+0

@NominalAnimal, True. Ho scritto questo prima di testare la funzione, poi userò mincore. – fons

5

"Il che spiega quello che vedo, ma ho un paio di domande:" "?? C'è un modo per rilevare se qualcosa è stato già mappato certo indirizzo senza accedere/proc/mappe"

Sì, utilizzare mmap senza MAP_FIXED.

"C'è un modo per forzare mmap a fallire nel caso di trovare pagine sovrapposte?"

Apparentemente no, ma utilizzare semplicemente munmap dopo mmap se mmap restituisce una mappatura diversa da quella richiesta.

Quando utilizzato senza MAP_FIXED, mmap sia su Linux e Mac OS X (e ho il sospetto anche altrove) obbedisce al parametro indirizzo se e solo se non mapping esistente nel range [indirizzo, indirizzo + lunghezza) esiste. Quindi se mmap risponde ad una mappatura a un indirizzo diverso da quello che si fornisce, si può dedurre che esiste già una mappatura in tale intervallo e che è necessario utilizzare un intervallo diverso. Poiché mmap in genere risponde a una mappatura a un indirizzo molto alto quando ignora il parametro dell'indirizzo, è sufficiente rimuovere la mappa utilizzando munmap e riprovare a un indirizzo diverso.

L'utilizzo del mincore per verificare l'utilizzo di un intervallo di indirizzi non è solo una perdita di tempo (è necessario sondare una pagina alla volta), potrebbe non funzionare. I kernel linux meno recenti falliscono mincore in modo appropriato per i mapping dei file. Non risponderanno a nulla per i mapping MAP_ANON. Ma come ho sottolineato, tutto ciò di cui hai bisogno è mmap e munmap.

Ho appena svolto questo esercizio nell'implementazione di un gestore di memoria per una VM Smalltalk. Io uso sbrk (0) per scoprire il primo indirizzo al quale è possibile associare il primo segmento, e quindi utilizzare mmap e un incremento di 1Mb per cercare spazio per segmenti successivi:

static long   pageSize = 0; 
static unsigned long pageMask = 0; 

#define roundDownToPage(v) ((v)&pageMask) 
#define roundUpToPage(v) (((v)+pageSize-1)&pageMask) 

void * 
sqAllocateMemory(usqInt minHeapSize, usqInt desiredHeapSize) 
{ 
    char *hint, *address, *alloc; 
    unsigned long alignment, allocBytes; 

    if (pageSize) { 
     fprintf(stderr, "sqAllocateMemory: already called\n"); 
     exit(1); 
    } 
    pageSize = getpagesize(); 
    pageMask = ~(pageSize - 1); 

    hint = sbrk(0); /* the first unmapped address above existing data */ 

    alignment = max(pageSize,1024*1024); 
    address = (char *)(((usqInt)hint + alignment - 1) & ~(alignment - 1)); 

    alloc = sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto 
       (roundUpToPage(desiredHeapSize), address, &allocBytes); 
    if (!alloc) { 
     fprintf(stderr, "sqAllocateMemory: initial alloc failed!\n"); 
     exit(errno); 
    } 
    return (usqInt)alloc; 
} 

/* Allocate a region of memory of at least size bytes, at or above minAddress. 
* If the attempt fails, answer null. If the attempt succeeds, answer the 
* start of the region and assign its size through allocatedSizePointer. 
*/ 
void * 
sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto(sqInt size, void *minAddress, sqInt *allocatedSizePointer) 
{ 
    char *address, *alloc; 
    long bytes, delta; 

    address = (char *)roundUpToPage((unsigned long)minAddress); 
    bytes = roundUpToPage(size); 
    delta = max(pageSize,1024*1024); 

    while ((unsigned long)(address + bytes) > (unsigned long)address) { 
     alloc = mmap(address, bytes, PROT_READ | PROT_WRITE, 
        MAP_ANON | MAP_PRIVATE, -1, 0); 
     if (alloc == MAP_FAILED) { 
      perror("sqAllocateMemorySegmentOfSizeAboveAllocatedSizeInto mmap"); 
      return 0; 
     } 
     /* is the mapping both at or above address and not too far above address? */ 
     if (alloc >= address && alloc <= address + delta) { 
      *allocatedSizePointer = bytes; 
      return alloc; 
     } 
     /* mmap answered a mapping well away from where Spur prefers. Discard 
     * the mapping and try again delta higher. 
     */ 
     if (munmap(alloc, bytes) != 0) 
      perror("sqAllocateMemorySegment... munmap"); 
     address += delta; 
    } 
    return 0; 
} 

Questo sembra funzionare bene , allocare memoria ad indirizzi crescenti saltando su qualsiasi mappatura esistente.

HTH