Ich weiß, dass das OP bereits eine der Antworten akzeptiert hat, aber leider erklärt es nicht, warum MAP_GROWSDOWN
scheint manchmal zu funktionieren. Da diese Stack Overflow-Frage einer der ersten Treffer in Suchmaschinen ist, möchte ich meine Antwort für andere hinzufügen.
Die Dokumentation von MAP_GROWSDOWN
muss aktualisiert werden. Insbesondere:
Dieses Wachstum kann wiederholt werden, bis die Abbildung auf eine Seite des oberen Endes der nächstniedrigeren Abbildung anwächst, wobei an diesem Punkt das Berühren der "Schutz"-Seite zu einem SIGSEGV-Signal führt.
In Wirklichkeit erlaubt der Kernel kein MAP_GROWSDOWN
Mapping, um näher als stack_guard_gap
zu wachsen Seiten von der vorherigen Zuordnung entfernt. Der Standardwert ist 256, kann aber auf der Kernel-Befehlszeile überschrieben werden. Da Ihr Code keine gewünschte Adresse für das Mapping angibt, wählt der Kernel automatisch eine aus, landet aber höchstwahrscheinlich innerhalb von 256 Seiten vom Ende eines bestehenden Mappings.
BEARBEITEN :
Darüber hinaus verweigern Kernel vor v5.0 den Zugriff auf eine Adresse, die mehr als 64k+256 Bytes unter dem Stapelzeiger liegt. Einzelheiten finden Sie in diesem Kernel-Commit.
Dieses Programm funktioniert auf x86 sogar mit Kerneln vor 5.0:
#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>
#define PAGE_SIZE 4096UL
#define GAP 512 * PAGE_SIZE
static void print_maps(void)
{
FILE *f = fopen("/proc/self/maps", "r");
if (f) {
char buf[1024];
size_t sz;
while ( (sz = fread(buf, 1, sizeof buf, f)) > 0)
fwrite(buf, 1, sz, stdout);
fclose(f);
}
}
int main()
{
char *p;
void *stack_ptr;
/* Choose an address well below the default process stack. */
asm volatile ("mov %%rsp,%[sp]"
: [sp] "=g" (stack_ptr));
stack_ptr -= (intptr_t)stack_ptr & (PAGE_SIZE - 1);
stack_ptr -= GAP;
printf("Ask for a page at %p\n", stack_ptr);
p = mmap(stack_ptr, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_STACK | MAP_ANONYMOUS | MAP_GROWSDOWN,
-1, 0);
printf("Mapped at %p\n", p);
print_maps();
getchar();
/* One page is already mapped: stack pointer does not matter. */
*p = 'A';
printf("Set content of that page to \"%s\"\n", p);
print_maps();
getchar();
/* Expand down by one page. */
asm volatile (
"mov %%rsp,%[sp]" "\n\t"
"mov %[ptr],%%rsp" "\n\t"
"movb $'B',-1(%%rsp)" "\n\t"
"mov %[sp],%%rsp"
: [sp] "+&g" (stack_ptr)
: [ptr] "g" (p)
: "memory");
printf("Set end of guard page to \"%s\"\n", p - 1);
print_maps();
getchar();
return 0;
}
Ersetzen:
volatile char *c_ptr_1 = mapped_ptr - 4096; //1 page below
Mit
volatile char *c_ptr_1 = mapped_ptr;
Denn:
Die Rücksprungadresse ist eine Seite tiefer als der Speicherbereich, der tatsächlich im virtuellen Adressraum des Prozesses angelegt wird. Durch Berühren einer Adresse auf der "Wächter"-Seite unterhalb der Zuordnung wird die Zuordnung um eine Seite erweitert.
Beachten Sie, dass ich die Lösung getestet habe und sie wie erwartet auf Kernel 4.15.0-45-generic funktioniert.
Zunächst einmal wollen Sie MAP_GROWSDOWN
nicht , und so funktioniert der Haupt-Thread-Stack nicht. Speicherzuordnung eines Prozesses mit pmap analysieren. [Stack] Nichts verwendet es und so ziemlich nichts sollte benutze es. Das Zeug in der Manpage, das besagt, dass es "für Stacks verwendet" wird, ist falsch und sollte behoben werden.
Ich vermute, dass es fehlerhaft sein könnte (weil nichts es verwendet, also normalerweise niemand sich darum kümmert oder es sogar bemerkt, wenn es kaputt geht.)
Ihr Code funktioniert für mich, wenn ich den mmap
ändere Anruf, um mehr als 1 Seite zuzuordnen. Insbesondere habe ich 4096 * 100
ausprobiert . Ich verwende Linux 5.0.1 (Arch Linux) auf Bare Metal (Skylake).
/proc/PID/smaps
zeigt eine gd
an Flagge.
Und dann (bei Single-Stepping des asm) maps
Der Eintrag ändert sich tatsächlich in eine niedrigere Startadresse, aber dieselbe Endadresse, sodass er buchstäblich nach unten wächst, wenn ich mit einem 400k-Mapping beginne. Dies ergibt eine Anfangszuweisung von 400.000 oben die Absenderadresse, die bei Ausführung des Programms auf 404 KB anwächst. (Die Größe für einen _GROWSDOWN
Zuordnung ist nicht die Wachstumsgrenze oder so etwas.)
https://bugs.centos.org/view.php?id=4767 könnte verwandt sein; zwischen den Kernelversionen in CentOS 5.3 und 5.5 hat sich etwas geändert. Und/oder es hatte etwas damit zu tun, in einer VM (5.3) zu arbeiten und nicht auf Bare Metal (5.5) zu wachsen und Fehler zu machen.
Ich habe das C vereinfacht, um ptr[-4095]
zu verwenden etc:
int main(void){
volatile char *ptr = mmap(NULL, 4096*100,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
-1, 0);
if(ptr == MAP_FAILED){
int error_code = errno;
fprintf(stderr, "Cannot do MAP_FIXED mapping."
"Error code = %d, details = %s\n", error_code, strerror(error_code));
exit(EXIT_FAILURE);
}
ptr[0] = 'a'; //address returned by mmap
ptr[-4095] = 'b'; // grow by 1 page
}
Kompilieren mit gcc -Og
gibt asm, das nett zu Single-Step ist.
Übrigens sind verschiedene Gerüchte über die Entfernung des Flags aus glibc offensichtlich falsch. Diese Quelle lässt sich kompilieren, und es ist klar, dass sie auch vom Kernel unterstützt und nicht stillschweigend ignoriert wird. (Obwohl das Verhalten, das ich bei der Größe 4096 statt 400 KB sehe, genau damit übereinstimmt, dass das Flag stillschweigend ignoriert wird. Der gd
VmFlag ist immer noch in smaps
vorhanden , also wird es in diesem Stadium nicht ignoriert.)
Ich überprüfte und es gab Platz für es zu wachsen, ohne in die Nähe einer anderen Zuordnung zu kommen. Also IDK, warum es nicht gewachsen ist, als das GD-Mapping nur 1 Seite war. Ich habe es ein paar Mal versucht und es ist jedes Mal segfaulted. Mit der größeren Anfangszuordnung hat es nie einen Fehler gegeben.
Beide Male wurde der mmap-Rückgabewert gespeichert (die erste Seite des eigentlichen Mappings), dann 4095 Bytes darunter gespeichert.