Przeciek pamięci w jak najmniejszej liczbie bajtów

79

Twoim zadaniem jest napisanie kodu, który wycieka co najmniej jeden bajt pamięci w jak najmniejszej liczbie bajtów. Pamięć musi zostać wyciekła, a nie tylko przydzielona .

Wyciek pamięci to pamięć, którą program przydziela, ale traci możliwość dostępu, zanim będzie mogła poprawnie zwolnić pamięć. W przypadku większości języków wysokiego poziomu pamięć ta musi być przydzielona na stercie.

Przykładem w C ++ może być następujący program:

int main(){new int;}

Powoduje to utworzenie new intstosu bez wskaźnika do niego. Ta pamięć jest natychmiast wyciekana, ponieważ nie mamy możliwości uzyskania do niej dostępu.

Oto, jak może wyglądać podsumowanie wycieku z Valgrind :

LEAK SUMMARY:
   definitely lost: 4 bytes in 1 blocks
   indirectly lost: 0 bytes in 0 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 0 bytes in 0 blocks
        suppressed: 0 bytes in 0 blocks

Wiele języków ma debugger pamięci (taki jak Valgrind ), jeśli możesz, powinieneś dołączyć dane wyjściowe z takiego debuggera, aby potwierdzić, że wyciekła pamięć.

Celem jest zminimalizowanie liczby bajtów w źródle.

Sriotchilism O'Zaic
źródło
2
Być może możesz mieć wyciek różnych przedziałów iw zależności od tego, ile wyciekniesz, stracisz x% liczby bajtów
Christopher
11
@ChristopherPeart Po pierwsze nie jestem fanem premii za wyzwania, a za dwa, jak już wykazaliście, bardzo łatwo jest wyciec nieograniczoną pamięć.
Sriotchilism O'Zaic
1
Związane . Nie jest to jednak duplikat, ponieważ większość odpowiedzi na to pytanie tworzy nieskończoną, dostępną strukturę pamięci, a nie przecieka pamięci.
2
jaki jest pomysł Czy mem nie może zostać uwolniony? Wydaje mi się, że wymagałoby to natywnego wykonania w przypadku języków odśmiecanych lub wykorzystywania błędów.
akostadinov
7
Widzę, jak języki zaprojektowane do gry w golfa zawodzą na tym ...
Kh40tiK

Odpowiedzi:

89

Perl (5.22.2), 0 bajtów

Wypróbuj online!

Wiedziałem, że będzie jakiś język, który wyciekł pamięć z pustego programu. Spodziewałem się, że będzie to esolang, ale okazuje się, że perlprzecieka pamięć w dowolnym programie. (Zakładam, że jest to celowe, ponieważ zwolnienie pamięci, jeśli wiesz, że i tak zamierzasz wyjść, to po prostu marnuje czas; w związku z tym obecnie powszechnym zaleceniem jest po prostu wyciekanie pozostałej pamięci, gdy znajdziesz się w procedurach wyjścia programu .)

Weryfikacja

$ echo -n | valgrind perl
snip
==18517== 
==18517== LEAK SUMMARY:
==18517==    definitely lost: 8,134 bytes in 15 blocks
==18517==    indirectly lost: 154,523 bytes in 713 blocks
==18517==      possibly lost: 0 bytes in 0 blocks
==18517==    still reachable: 0 bytes in 0 blocks
==18517==         suppressed: 0 bytes in 0 blocks
==18517== 
==18517== For counts of detected and suppressed errors, rerun with: -v
==18517== ERROR SUMMARY: 15 errors from 15 contexts (suppressed: 0 from 0)

źródło
16
Podobała mi się odpowiedź Unlambda, ale ta wersja (IMHO) jest zbyt rozciągnięta, ponieważ to oczywiście interpreter przecieka pamięć, tzn. „Zdecydowanie się gubię: 7 742 bajtów w 14 blokach”, kiedy uruchamiam perl --versionna mojej maszynie , mimo że nigdy nie uruchamia żadnego programu.
zeppelin
11
@zeppelin: Zgoda, ale zgodnie z naszymi zasadami, to implementacja określa język, więc jeśli implementacja wycieknie pamięci, wszystkie programy w pamięci wyciek języka. Niekoniecznie jestem pewien, czy zgadzam się z tą zasadą, ale w tym momencie jest to zbyt głęboko zakorzenione, aby naprawdę móc to zmienić.
8
Działa to również w węźle JS.
Dennis
6
To wydaje się jak nowa standardowa luka w tworzeniu ...
Michael Hampton
46
Wreszcie skrypt Perla, który mogę zrozumieć.
user11153
66

C, 48 31 22 bajtów

Ostrzeżenie: nie uruchamiaj tego zbyt wiele razy.

Podziękowania dla Dennisa za wiele pomocy / pomysłów!

f(k){shmget(k,1,512);}

To idzie o krok dalej. shmgetprzydziela pamięć współdzieloną, która nie jest zwalniana po zakończeniu programu. Używa klucza do identyfikacji pamięci, więc używamy niezainicjowanej int. Jest to technicznie niezdefiniowane zachowanie, ale praktycznie oznacza to, że używamy wartości, która znajduje się tuż nad szczytem stosu, gdy jest ona wywoływana. Zostanie to zapisane przy następnym dodaniu czegokolwiek do stosu, więc stracimy klucz.


Jedynym przypadkiem, że to nie działa, jest to, że możesz dowiedzieć się, co było wcześniej na stosie. W przypadku dodatkowych 19 bajtów można uniknąć tego problemu:

f(){srand(time(0));shmget(rand(),1,512);}

Lub dla 26 bajtów:

main(k){shmget(&k,1,512);}

Ale w tym przypadku pamięć wycieka po wyjściu z programu. Podczas działania program ma dostęp do pamięci, co jest niezgodne z regułami, ale po zakończeniu programu tracimy dostęp do klucza i pamięć jest nadal przydzielana. Wymaga to randomizacji układu przestrzeni adresowej (ASLR), w przeciwnym razie &kzawsze będzie to samo. Obecnie ASLR jest zazwyczaj domyślnie włączony.


Weryfikacja:

Możesz użyć, ipcs -maby zobaczyć, jaka pamięć współdzielona istnieje w twoim systemie. Dla przejrzystości usunąłem wcześniej istniejące wpisy:

$ cat leakMem.c 
f(k){shmget(k,1,512);}
int main(){f();}     
$ gcc leakMem.c -o leakMem
leakMem.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 f(k){shmget(k,1,512);}
 ^
leakMem.c: In function ‘f’:
leakMem.c:1:1: warning: type of ‘k’ defaults to ‘int’ [-Wimplicit-int]
leakMem.c:1:6: warning: implicit declaration of function ‘shmget’ [-Wimplicit-function-declaration]
 f(k){shmget(k,1,512);}
ppcg:ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      


$ ./leakMem 

$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      

0x0000007b 3375157    Riley      0          1          0  
Riley
źródło
1
@AndrewSavinykh Teoretycznie shmid mógł być przechowywany w pliku, a program mógłby dołączyć do niego w przyszłości. Tak działa pamięć współdzielona unix ...
tbodt
1
@AndrewSavinykh Pamięć współdzielona zasadniczo staje się zasobem, który system operacyjny może przekazywać innym procesom. Jest podobny do pliku znajdującego się w pamięci RAM i każdy proces, który zna nazwę (klucz), ma do niego dostęp, dopóki nie zostanie usunięty. Wyobraź sobie proces, który oblicza liczbę i przechowuje ją w pamięci i kończy działanie, zanim proces odczytujący dane połączy się z pamięcią współdzieloną. W takim przypadku, jeśli system operacyjny zwolni pamięć, drugi proces nie może jej uzyskać.
Riley
35
Dziękujemy za opublikowanie tego. Właśnie zabezpieczyłem TIO przed wyciekiem pamięci współdzielonej.
Dennis
4
@Dennis Dlatego nie opublikowałem linku do TIO. Nie wiedziałem, czy to jest chronione, czy nie.
Riley,
12
Podoba mi się, jak używasz słowa problem do opisania scenariusza, w którym program wycieka mniej pamięci, niż zamierzano.
kasperd
40

Unlambda ( c-refcnt/unlambda), 1 bajt

i

Wypróbuj online!

To naprawdę wyzwanie, aby znaleźć istniejącego interpretera, który przecieka pamięć w bardzo prostych programach. W tym przypadku użyłem Unlambda. Istnieje więcej niż jeden oficjalny interpreter Unlambda, ale c-refcntjest to jeden z najłatwiejszych do zbudowania i ma tę użyteczną właściwość, że przecieka pamięć po pomyślnym uruchomieniu programu. Więc wszystko, co musiałem tu podać, to najprostszy możliwy legalny program Unlambda, brak możliwości. (Zwróć uwagę, że pusty program tutaj nie działa; pamięć jest nadal dostępna w momencie awarii tłumacza).

Weryfikacja

$ wget ftp://ftp.madore.org/pub/madore/unlambda/unlambda-2.0.0.tar.gz
…fantastyczna okazja…
18.02.2017, 18:11:08 (975 KB / s) - zapisano „unlambda-2.0.0.tar.gz” [492894]
$ tar xf unlambda-2.0.0.tar.gz 
$ cd unlambda-2.0.0 / c-refcnt /
$ gcc unlambda.c
$ echo -ni | valgrind ./a.out / dev / stdin
…fantastyczna okazja…
== 3417 == PODSUMOWANIE WYCIEKU:
== 3417 == zdecydowanie stracone: 40 bajtów w 1 blokach
== 3417 == pośrednio utracone: 0 bajtów w 0 blokach
== 3417 == prawdopodobnie utracone: 0 bajtów w 0 blokach
== 3417 == wciąż osiągalny: 0 bajtów w 0 blokach
== 3417 == pomijane: 0 bajtów w 0 blokach
== 3417 == 
== 3417 == Aby uzyskać liczbę wykrytych i pomijanych błędów, uruchom ponownie z: -v
== 3417 == PODSUMOWANIE BŁĘDU: 1 błędy z 1 kontekstów (pomijane: 0 z 0)

źródło
39

TI-Basic, 12 bajtów

While 1
Goto A
End
Lbl A
Pause 

„... przeciek pamięci to miejsce, w którym używasz Goto / Lbl w pętli lub warunkowo (cokolwiek, co ma polecenie End), aby wyskoczyć z tej struktury kontrolnej przed osiągnięciem polecenia End ...” (więcej)

Timtech
źródło
7
Wow, chyba to pamiętam. Ciągle wyskakiwałem z pętli w moich starych podstawowych programach i zauważyłem, jak mój TI-84 + zwalnia i spowalnia ...
Ray
2
Tak, większość z nas zna to uczucie;) @RayKoopa
Timtech
13
+1 dla Ti Basic. Większość mojego 9 roku klasy spędziłem na programowaniu tych rzeczy.
markasoftware
Czy potrzebujesz Pause na końcu? Możesz zapisać 2 bajty.
kamoroso94
@ kamoroso94 Myślę, że tak, ponieważ „Jeśli program zostanie zakończony, wyciek zostanie usunięty i nie spowoduje dalszych problemów”, więc ma to zatrzymać program przed zakończeniem.
Timtech
32

Python <3.6.5, 23 bajty

property([]).__init__()

property.__init__przecieki odniesienia do nieruchomości jest stary fget, fset, fdel, i __doc__jeśli nazywają go już przygotowanej propertyinstancji. Jest to błąd, który ostatecznie został zgłoszony jako część problemu CPython 31787 i naprawiony w Python 3.6.5 i Python 3.7.0 . (Tak, property([])można to zrobić.)

użytkownik2357112
źródło
Czy wysłano raport o błędzie?
mbomb007,
1
@ mbomb007: CPython issue 31787.
user2357112
27

C #, 34 bajty

class L{~L(){for(;;)new L();}}

To rozwiązanie nie wymaga sterty. Po prostu potrzebuje naprawdę ciężko pracującego GC ( Garbage Collector ).

Zasadniczo zmienia GC we własnego wroga.

Wyjaśnienie

Za każdym razem, gdy wywoływany jest destruktor , tworzy nowe instancje tej klasy zła, o ile upłynie limit czasu i każe GC po prostu porzucić ten obiekt, nie czekając na zakończenie destruktora. Do tego czasu powstały tysiące nowych instancji.

„Zło” tego polega na tym, że im trudniej działa GC, tym bardziej będzie to wybuchało na twojej twarzy.

Oświadczenie : Twój GC może być mądrzejszy od mojego. Inne okoliczności w programie mogą spowodować, że GC zignoruje pierwszy obiekt lub jego destruktor. W takich przypadkach nie wybuchnie. Ale w wielu odmianach tak będzie . Dodanie kilku bajtów tutaj i może zapewnić wyciek w każdych możliwych okolicznościach. Może poza wyłącznikiem zasilania.

Test

Oto pakiet testowy :

using System;
using System.Threading;
using System.Diagnostics;
class LeakTest {
    public static void Main() {
        SpawnLeakage();
        Console.WriteLine("{0}-: Objects may be freed now", DateTime.Now);
        // any managed object created in SpawbLeakage 
        //  is no longer accessible
        // The GC should take care of them

        // Now let's see
        MonitorGC();
    }
    public static void SpawnLeakage() {
        Console.WriteLine("{0}-: Creating 'leakage' object", DateTime.Now);
        L l = new L();
    }
    public static void MonitorGC() {
        while(true) {
            int top = Console.CursorTop;
            int left = Console.CursorLeft;
            Console.WriteLine(
                "{0}-: Total managed memory: {1} bytes",
                DateTime.Now,
                GC.GetTotalMemory(false)
            );
            Console.SetCursorPosition(left, top);
        }
    }
}

Wyjście po 10 minutach:

2/19/2017 2:12:18 PM-: Creating 'leakage' object
2/19/2017 2:12:18 PM-: Objects may be freed now
2/19/2017 2:22:36 PM-: Total managed memory: 2684476624 bytes

To 2 684 476 624 bajtów. Cały WorkingSetproces wynosił około 4,8 GB

Ta odpowiedź została zainspirowana cudownym artykułem Erica Lipperta: Kiedy wszystko, co wiesz, jest złe .

MrPaulch
źródło
To jest fascynujące. Czy śmieciarz „zapomina” o istnieniu niektórych rzeczy i przez to gubi się? Nie wiem dużo o c #. Zastanawiam się też, jaka jest różnica między bombą a przeciekiem? Wyobrażam sobie podobne fiasko, które można by wywołać, wywołując konstruktor z wnętrza konstruktora lub mając nieskończoną funkcję rekurencyjną, która nigdy się nie zatrzymuje, chociaż technicznie system nigdy nie traci dostępu do tych odniesień, po prostu zabraknie mu miejsca ...
don jasny
1
Konstruktor wewnątrz konstruktora spowodowałby przepełnienie stosu. Ale destruktor instancji zostaje wywołany w płaskiej hierarchii. GC tak naprawdę nigdy nie gubi obiektów. Kiedy tylko próbuje je zniszczyć, nieświadomie tworzy nowe obiekty. Z drugiej strony kod użytkownika nie ma dostępu do wspomnianych obiektów. Mogą również pojawić się wspomniane niespójności, ponieważ GC może zdecydować o zniszczeniu obiektu bez wywoływania swojego destruktora.
MrPaulch,
Czy samo wyzwanie nie byłoby kompletne class L{~L(){new L();}}? AFAIK for(;;)sprawia, że ​​pamięć przecieków jest szybsza, prawda?
BgrWorker,
1
Niestety nie. Ponieważ dla każdego zniszczonego obiektu zostanie utworzona tylko jedna nowa instancja, która następnie będzie niedostępna i oznaczona do zniszczenia. Powtarzać. Tylko jeden obiekt będzie czekał na zniszczenie. Brak rosnącej populacji.
MrPaulch
2
Nie całkiem. W końcu jeden sfinalizowany zostanie zignorowany. Odpowiedni obiekt zostanie zjedzony niezależnie.
MrPaulch
26

C (gcc) , 15 bajtów

f(){malloc(1);}

Weryfikacja

$ cat leak.c
f(){malloc(1);}
main(){f();}
$ gcc -g -o leak leak.c
leak.c: In function ‘f’:
leak.c:1:5: warning: incompatible implicit declaration of built-in function ‘malloc’ [enabled by default]
 f(){malloc(1);}
     ^
$ valgrind --leak-check=full ./leak
==32091== Memcheck, a memory error detector
==32091== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==32091== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==32091== Command: ./leak
==32091==
==32091==
==32091== HEAP SUMMARY:
==32091==     in use at exit: 1 bytes in 1 blocks
==32091==   total heap usage: 1 allocs, 0 frees, 1 bytes allocated
==32091==
==32091== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==32091==    at 0x4C29110: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==32091==    by 0x40056A: f (leak.c:1)
==32091==    by 0x40057A: main (leak.c:2)
==32091==
==32091== LEAK SUMMARY:
==32091==    definitely lost: 1 bytes in 1 blocks
==32091==    indirectly lost: 0 bytes in 0 blocks
==32091==      possibly lost: 0 bytes in 0 blocks
==32091==    still reachable: 0 bytes in 0 blocks
==32091==         suppressed: 0 bytes in 0 blocks
==32091==
==32091== For counts of detected and suppressed errors, rerun with: -v
==32091== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Dennis
źródło
26

JavaScript, 14 bajtów

Grał w golfa

setInterval(0)

Rejestruje pustą procedurę obsługi przedziałów z domyślnym opóźnieniem, odrzucając wynikowy identyfikator timera (uniemożliwiając anulowanie).

wprowadź opis zdjęcia tutaj

Użyłem innego niż domyślny interwału, aby stworzyć kilka milionów timerów, aby zilustrować wyciek, ponieważ użycie domyślnego interwału zjada procesor jak szalony.

zepelin
źródło
5
Haha Uwielbiam to, że wpisałeś „Golfed”, co mnie ciekawi wersji bez golfa
Martijn,
9
może tak wyglądaćif(window && window.setInterval && typeof window.setInterval === 'function') { window.setInterval(0); }
Tschallacka
3
W rzeczywistości nie jest to niemożliwe do anulowania: identyfikatory interwału (i limitu czasu) są numerowane sekwencyjnie, więc dość łatwo jest anulować rzecz po prostu dzwoniąc clearIntervalz rosnącym identyfikatorem, dopóki nie minie interwał. Na przykład:for(let i=0;i<1e5;i++){try{clearInterval(i);}catch(ex){}}
user2428118
5
@ user2428118 Jak mówi zeppelin, nie jest to bardziej „prawowity” niż stwierdzenie, że wycieki C / C ++ nie są „prawdziwe”, ponieważ można free()
brutalnie
1
Wow, nie ma wielu wyzwań, w których JavaScript jest prawdziwym konkurentem ...
Jared Smith
19

Java, 10 bajtów

Wreszcie konkurencyjna odpowiedź w Javie!

Grał w golfa

". "::trim

To jest odwołanie do metody (w stosunku do stałej ciągu), którego można użyć w następujący sposób:

Supplier<String> r = ". "::trim

Ciągiem znaków ". "zostanie automatycznie dodany do światowej internowany strings basenie, jak prowadzony przez java.lang.Stringklasy, a jak immediatelly przyciąć go, odniesienie do niego nie mogą być ponownie wykorzystane w dalszej części kodu (chyba, że deklarują dokładnie ten sam ciąg ponownie).

...

Pula ciągów, początkowo pusta, jest prywatnie utrzymywana przez klasę String.

Wszystkie ciągi literalne i wyrażenia stałe o wartościach ciągowych są internalizowane. Literały łańcuchowe są zdefiniowane w sekcji 3.10.5 specyfikacji języka Java ™.

...

https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#intern--

Możesz to zmienić w wyciek pamięci „na poziomie produkcyjnym”, dodając ciąg do siebie, a następnie jawnie wywołując metodę intern () w pętli.

zepelin
źródło
2
Rozważyłem to dla C # ... ale nie sądzę, żeby to się liczyło, ponieważ, jak mówisz, możesz uzyskać dostęp do tej pamięci, włączając inny literał łańcuchowy. Byłbym również zainteresowany tym, co ("." + " ").intern()by zrobił (gdyby były one wprowadzane przez użytkownika lub w / e, więc pomijamy optymalizacje kompilatora).
VisualMelon,
1
Rzeczywiście, jedyny konsensus jest w najlepszym razie niewielki, jestem po prostu zdecydowanie po stronie „kod powinien się skompilować”. Nadal nie jestem pewien, czy kupuję to rozwiązanie, biorąc pod uwagę sformułowanie pytania (tych ciągów nie można uwolnić i można je znaleźć w normalnym kodzie operacyjnym, nawet jeśli jest to mało prawdopodobne), ale zapraszam innych do dokonania własnego osądu
VisualMelon,
3
Ten sznurek nie jest nawet niedostępny , nie mówiąc już o wycieku. Możemy go odzyskać w dowolnym momencie, internalizując równy ciąg. Gdyby to liczyć, każda nieużywana zmienna globalna (prywatna statyczna w Javie) byłaby nieszczelna. Nie tak definiuje się wycieki pamięci.
user2357112,
3
@ user2357112 „... Ten ciąg nie jest nawet niedostępny ...” Wygląda to oczywisto, ponieważ widzisz kod. Rozważmy teraz, że masz odniesienie do metody X () jako argumentu do swojego kodu, wiesz, że alokuje on (i internuje) dosłowny ciąg znaków w środku, ale nie wiesz, który dokładnie, może to być „.” Lub „123” lub dowolny inny ciąg ((ogólnie) nieznanej długości. Czy mógłbyś zademonstrować, w jaki sposób nadal możesz uzyskać do niego dostęp, lub cofnąć przydział wpisu w puli „intern”, którą zajmuje?
zeppelin
2
@ user2357112 Na komputerze ze skończoną pamięcią można uzyskać dostęp do wartości zapisanej w dowolnym fragmencie pamięci simply by guessing it correctly, ale to nie znaczy, że nie ma czegoś takiego jak wycieki pamięci. there's probably some way to use reflection to determine the string's contents toomógłbyś to zademonstrować? (wskazówka: String.intern () jest zaimplementowany w natywnym kodzie).
zeppelin
17

Rdza, 52 bajty

extern{fn malloc(_:u8);}fn main(){unsafe{malloc(9)}}

Przydziela niektóre bajty za pomocą systemowego malloc. Zakłada się, że niewłaściwy ABI jest dopuszczalny.


Rdza (teoretycznie), 38 bajtów

fn main(){Box::into_raw(Box::new(1));}

Przydzielamy pamięć na stercie, wyodrębniamy surowy wskaźnik, a następnie po prostu go ignorujemy, skutecznie przeciekając. ( Box::into_rawjest wtedy krótszy std::mem::forget).

Jednak Rust domyślnie używa jemalloc, którego valgrind nie może wykryć żadnego wycieku . Moglibyśmy przejść do alokatora systemu, ale to dodaje 50 bajtów i wymaga co noc. Wielkie dzięki za bezpieczeństwo pamięci.


Wyjście pierwszego programu:

==10228== Memcheck, a memory error detector
==10228== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10228== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==10228== Command: ./1
==10228== 
==10228== 
==10228== HEAP SUMMARY:
==10228==     in use at exit: 9 bytes in 1 blocks
==10228==   total heap usage: 7 allocs, 6 frees, 2,009 bytes allocated
==10228== 
==10228== LEAK SUMMARY:
==10228==    definitely lost: 9 bytes in 1 blocks
==10228==    indirectly lost: 0 bytes in 0 blocks
==10228==      possibly lost: 0 bytes in 0 blocks
==10228==    still reachable: 0 bytes in 0 blocks
==10228==         suppressed: 0 bytes in 0 blocks
==10228== Rerun with --leak-check=full to see details of leaked memory
==10228== 
==10228== For counts of detected and suppressed errors, rerun with: -v
==10228== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
kennytm
źródło
niesamowite. posty tego typu skłoniły mnie do odkrycia Rust w ciągu ostatniego roku, zdecydowanie jednego z najfajniejszych języków, których próbowałem się nauczyć.
don bright
16

8086 ASM, 3 bajty

W tych przykładach założono, że środowisko wykonawcze C jest połączone.

jmp _malloc

łączy się z tym, e9 XX XXgdzie XX XXjest względny adres_malloc

Powoduje mallocto przydzielenie nieprzewidzianej ilości pamięci, a następnie natychmiast powraca, kończąc procesy. W niektórych systemach operacyjnych, takich jak DOS, pamięć może w ogóle nie zostać odzyskana, dopóki system nie zostanie ponownie uruchomiony!

FUZxxl
źródło
Normalna implementacja malloc spowoduje zwolnienie pamięci przy wyjściu z procesu.
Joshua
@Joshua Tak, ale to zachowanie zdefiniowane w implementacji.
FUZxxl,
12

Dalej, 6 bajtów

Grał w golfa

s" " *

Przydziela pusty ciąg znaków s" ", pozostawiając jego stos i długość (0) na stosie, a następnie mnoży je (powodując utratę adresu pamięci).

Valgrind

%valgrind --leak-check=full gforth -e 's" " * bye'
...
==12788== HEAP SUMMARY:
==12788==     in use at exit: 223,855 bytes in 3,129 blocks
==12788==   total heap usage: 7,289 allocs, 4,160 frees, 552,500 bytes allocated
==12788== 
==12788== 1 bytes in 1 blocks are definitely lost in loss record 1 of 22
==12788==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12788==    by 0x406E39: gforth_engine (in /usr/bin/gforth-0.7.0)
==12788==    by 0x41156A: gforth_go (in /usr/bin/gforth-0.7.0)
==12788==    by 0x403F9A: main (in /usr/bin/gforth-0.7.0)
==12788== 
...
==12818== LEAK SUMMARY:
==12818==    definitely lost: 1 bytes in 1 blocks
==12818==    indirectly lost: 0 bytes in 0 blocks
zepelin
źródło
10

przejść 45 bajtów

package main
func main(){go func(){for{}}()}

tworzy to anonimowy goroutine z nieskończoną pętlą w nim. program może nadal działać normalnie, ponieważ uruchomienie goroutine przypomina trochę odrodzenie równolegle działającego małego wątku, ale program nie ma możliwości odzyskania pamięci przydzielonej dla goroutine. śmieciarz też go nie odbierze, ponieważ nadal działa. niektórzy nazywają to „wyciekiem goroutine”

Don Bright
źródło
czek golfowy: jest to 2 bajty krótsze niż dzwonienie C.malloc(8), ponieważ musiszimport"C"
Riking
9

Java 1.3, 23 bajty

void l(){new Thread();}

Tworzenie wątku, ale nie uruchamianie go. Wątek jest zarejestrowany w wewnętrznej puli wątków, ale nigdy nie zostanie uruchomiony, więc nigdy się nie zakończył, a zatem nigdy nie będzie kandydatem do GC. Jest to obiekt niemożliwy do odzyskania, utknął w limbos Java.

Jest to błąd w Javie do wersji 1.3, ponieważ został później naprawiony.

Testowanie

Poniższy program upewnia się, że pamięć zostanie zanieczyszczona nowymi obiektami wątku i pokaże zmniejszające się wolne miejsce w pamięci. Ze względu na testy szczelności intensywnie uruchamiam GC.

public class Pcg110485 {

    static
    void l(){new Thread();}

    public static void main(String[] args) {

        while(true){
            l();
            System.gc();
            System.out.println(Runtime.getRuntime().freeMemory());
        }
    }
}
Olivier Grégoire
źródło
Ponieważ działa to tylko w określonych wersjach Java, w nagłówku należy powiedzieć „Java 3”.
5
Nie ma czegoś takiego jak Java 3. To Java 1.3. Była Java 1.0, 1.1, 2, 1.3, 1.4, 5, 6, 7, 8, 9. Dziwna numeracja, ale tak właśnie jest.
Olivier Grégoire,
To też był mój pomysł. Cholera.
Magic Octopus Urn
8

Befunge ( grzyby ), 1 bajt

$

Może to zależeć od platformy i wersji (testowałem tylko z wersją 1.0.4 w systemie Windows), ale grzyby w przeszłości były bardzo nieszczelnym interpretatorem. Polecenie $(drop) nie powinno robić nic na pustym stosie, ale zapętlenie tego kodu w jakiś sposób bardzo szybko powoduje wyciek dużej ilości pamięci. W ciągu kilku sekund zużyje kilka koncertów i zawiesi się z błędem „brak pamięci”.

Zauważ, że niekoniecznie musi to być $polecenie - prawie wszystko by zrobił. Jednak nie będzie działać z pustym plikiem źródłowym. Musi być co najmniej jedna operacja.

James Holderness
źródło
8

Szybkie 3, 38 bajtów

Nowa wersja:

class X{var x: X!};do{let x=X();x.x=x}

x ma silne odniesienie do siebie, więc nie zostanie zwolniony, co spowoduje wyciek pamięci.

Stara wersja:

class X{var y:Y!}
class Y{var x:X!}
do{let x=X();let y=Y();x.y=y;y.x=x}

xzawiera silne odniesienie do yi na odwrót. Zatem żadne z nich nie zostanie zwolnione, co spowoduje wyciek pamięci.

Daniel
źródło
Hmm, nadal możesz odwoływać się do tej pamięci xi y, więc tak naprawdę nie wygląda to na wyciek (chyba, że ​​jakoś ją zniszczysz).
zeppelin
@zeppelin Ostatni wiersz można zawinąć w funkcję, aby to naprawić
NobodyNada
@NobodyNada, gdybym umieścił ostatnią linię w dobloku, który rozwiązałby problem podniesiony przez Zeppelina, prawda?
Daniel
@Dopapp Tak; też doby działało. Dobry pomysł!
NobodyNada
Można go skrócić, nie potrzebujesz dwóch klas - X może zawierać odniesienie do siebie:class X{var x: X!};do{let x=X();x.x=x}
Sebastian Osiński
7

Delphi (Object Pascal) - 33 bajty

Tworzenie obiektu bez zmiennej, pełnej konsoli programu:

program;begin TObject.Create;end.

Włączenie FastMM4 w projekcie pokaże wyciek pamięci:

wprowadź opis zdjęcia tutaj

R & D
źródło
6

C # - 84 bajtów

class P{static void Main(){System.Runtime.InteropServices.Marshal.AllocHGlobal(1);}}

Przydziela dokładnie 1 bajt niezarządzanej pamięci, a następnie traci IntPtr, co, jak sądzę, jest jedynym sposobem na uzyskanie lub uwolnienie jej. Możesz to przetestować, umieszczając go w pętli i czekając na awarię aplikacji (możesz dodać kilka zer, aby przyspieszyć działanie).

Uważałem System.IO.File.Create("a");i takie, ale nie jestem przekonany, że są to koniecznie wycieki pamięci, a sama aplikacja będzie zbierać pamięć, to system operacyjny pod który może wyciekać (bo Closealbo Disposenie były nazywane). Dostęp do plików wymaga również uprawnień systemu plików i nikt nie chce na nich polegać. I okazuje się, że i tak nie wycieknie, ponieważ nic nie stoi na przeszkodzie, aby wywołać finalizator (co uwalnia możliwe zasoby podstawowe), co obejmuje ramy w celu złagodzenia tego rodzaju błędów w ocenie (do pewnego stopnia), oraz pomylić programistów z pozornie niedeterministycznym blokowaniem plików (jeśli jesteś cynikiem). Podziękowania dla Jona Hanny za postawienie mnie w tej sprawie.

Jestem trochę rozczarowany, że nie mogę znaleźć krótszej drogi. .NET GC działa, nie mogę wymyślić żadnego IDisposablesw mscorlib, który na pewno wyciekłby (i rzeczywiście wszyscy wydają się mieć finalizatory, jak denerwujące) , nie znam żadnego innego sposobu przydzielania niezarządzanej pamięci (oprócz PInvoke ), a odbicie gwarantuje niczego z odniesieniem do niego (bez semantyki języka (np prywatnych członków lub klas z żadnym dostępowych)) może zostać znaleziony.

VisualMelon
źródło
1
System.IO.File.Create("a")nic nie wycieknie, ale GC.SuppressFinalize(System.IO.File.Create("a"))zrobi to, ponieważ jest wyraźnie proszone, aby nie uruchamiać finalizatora FileStreamwyprodukowanego.
Jon Hanna,
@JonHanna ma rację. Wydaje mi się, że moja paranoja IDisposable mnie pokonała.
VisualMelon,
Możesz mieć szansę na wyciek z GDI + za pomocą System.Drawing.Bitmap. Nie wiem, czy to się liczy, ponieważ to biblioteka systemu Windows powoduje wyciek, a nie sam program.
BgrWorker
@BgrWorker bez wątpienia mają również finalizator, a ja zwykle unikam zewnętrznych bibliotek w golfie kodowym, ponieważ nie zgadzam się z konsensusem w sprawie ich kosztu: jeśli możesz znaleźć sposób, w jaki jesteś pewien, możesz wysłać post to we własnej odpowiedzi!
VisualMelon,
<!-- language: lang-c# -->Dzięki za tę i miłą odpowiedź! (To C #, więc uwielbiam to)
Metoniem
5

Współczynnik , 13 bajtów

Factor ma automatyczne zarządzanie pamięcią, ale daje także dostęp do niektórych funkcji libc:

1 malloc drop

Ręcznie przydziela 1 bajt pamięci, zwraca jej adres i upuszcza.

malloc faktycznie rejestruje kopię, aby śledzić wycieki pamięci i podwójnie zwalnia, ale zidentyfikowanie tego, co wyciekłeś, nie jest łatwym zadaniem.

Jeśli wolisz się upewnić, że naprawdę zgubiłeś to odniesienie:

1 (malloc) drop

Testowanie wycieków za pomocą [ 1 malloc drop ] leaks.mówi:

| Disposable class | Instances |                    |
| malloc-ptr       | 1         | [ List instances ] |

Testowanie wycieków za pomocą [ 1 (malloc) drop ] leaks.mówi:

| Disposable class | Instances | |

O nie! Zły czynnik, teraz ma Alzheimera! RE:

Fede s.
źródło
5

Common Lisp (tylko SBCL), 28 26 bajtów

sb-alien::(make-alien int)

Uruchamiasz to tak sbcl --eval 'sb-alien::(make-alien int)':; nic nie jest drukowane ani zwracane, ale następuje przydzielenie pamięci. Jeśli owinę formularz w a (print ...), wskaźnik zostanie wyświetlony w REPL.

  1. package::(form)jest specjalnym zapisem w SBCL do tymczasowego wiązania bieżącego pakietu podczas czytania formularza. Służy to tutaj, aby uniknąć prefiksu zarówno z, jak make-alieniz . Myślę, że byłoby oszustwo, zakładając, że bieżący pakiet jest ustawiony na ten, ponieważ tak nie jest podczas uruchamiania.intsb-alien

  2. make-alien przydziela pamięć dla danego typu i opcjonalnego rozmiaru (przy użyciu malloc).

  3. Wykonując to w REPL, dodaj 0po alokacji, aby REPL nie zwracał wskaźnika, ale tę wartość. W przeciwnym razie, że nie będzie to prawdziwy przeciek ponieważ REPL pamięta ostatnie trzy zwrócone wartości (patrz *, **,*** ) i wciąż może mieć szansę zwolnić przydzieloną pamięć.

2 bajty usunięte dzięki PrzemysławP, dzięki!

rdzeń rdzeniowy
źródło
1
Można nie stosować 1(lub 2, 3etc.), a nie ()tak, że wartość zwracana 1? Pozwoliłoby to zaoszczędzić 1 bajt. Czy ta odpowiedź jest tylko REPL? Może jeśli załadujesz kod load, nie możesz go dołączyć ()ani nic na końcu, ponieważ i tak nie będzie dostępny?
PrzemysławP,
1
@ PrzemysławP Masz rację w obu kwestiach, próbowałem evali działa to tak, jak powiedziałeś. Wielkie dzięki!
coredump
4

AutoIt , 39 bajtów

#include<Memory.au3>
_MemGlobalAlloc(1)

Przydziela jeden bajt ze sterty. Ponieważ zwracany uchwyt _MemGlobalAllocjest odrzucany, nie ma możliwości jawnego zwolnienia tego przydziału.

mınxomaτ
źródło
3

C ++, 16 bajtów

main(){new int;}

Nie mam Valgrinda, aby sprawdzić, czy nie ma wycieków, ale jestem pewien, że powinien. W przeciwnym razie spróbowałbym:

main(){[]{new int;}();}

Wynik Valgrind

(Rzeczywiście przecieka)

==708== LEAK SUMMARY:
==708==    definitely lost: 4 bytes in 1 blocks
Matovitch
źródło
@WheatWizard Używam g++ 4.3.2(nie najnowszy) i kompiluje się dobrze. intDomyślnie żaden typ zwrotu nie jest . Ze -Wallmam ostrzeżenia:plop.cpp:1: warning: ISO C++ forbids declaration of 'main' with no type
matovitch
2
@WheatWizard Przepraszamy, właśnie widziałem, że podałeś przykład c ++, aby rozpocząć konkurs. Pochodząc z reddit, tylko patrzyłem na odpowiedzi i (o dziwo) nie widziałem żadnego C ++. Czuję się trochę głupio. : /
matovitch
Konsensus jest taki, że można liczyć tylko []{new int;}jako funkcję C ++ (wyzwanie nie określiło całego programu).
Toby Speight
3

Java (OpenJDK 9) , 322 220 bajtów

import sun.misc.*;class Main{static void main(String[]a)throws Exception{java.lang.reflect.Field f=Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible‌​(1<2);((Unsafe)f.get‌​(1)).allocateMemory(‌​1);}}

Wypróbuj online!

Jest to inny wyciek pamięci, który nie korzysta z pamięci podręcznej ciągów. Przydziela połowę pamięci RAM i nie możesz nic z tym zrobić.

Dzięki Zeppelin za uratowanie wszystkich bajtów

Serverfrog
źródło
Możesz zapisać kilka bajtów, uzyskując Unsafeinstancję ze zmiennej statycznej w jej wnętrzu, w ten sposób:import sun.misc.*;class M{static void main(String[]a)throws Exception{java.lang.reflect.Field f=Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(1<2);((Unsafe)f.get(1)).allocateMemory(1);}}
zeppelin
I możesz zaoszczędzić trochę, zastępując public static void mainstatyczny inicjator static{try{}catch(Exception e){}}(który może być nieco trudniejszy do uruchomienia, ale mimo to jest prawidłowy i kompilowalny).
zeppelin
yhea użycie konstruktora zostało użyte w wersji Androida, z której korzystałem.
Zmienię
Usuń białe znaki, użyj azamiast argsi usuń publiczne. tio.run/nexus/…
Pavel
true można zastąpić 1> 0
masterX244
3

c, 9 bajtów

main(){}

Dowód:

localhost/home/elronnd-10061: cat t.c
main(){}
localhost/home/elronnd-10062: valgrind gcc t.c
==10092== Memcheck, a memory error detector
==10092== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10092== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==10092== Command: gcc t.c
==10092==
t.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main(){}
 ^~~~
==10092==
==10092== HEAP SUMMARY:
==10092==     in use at exit: 178,518 bytes in 73 blocks
==10092==   total heap usage: 362 allocs, 289 frees, 230,415 bytes allocated
==10092==
==10092== LEAK SUMMARY:
==10092==    definitely lost: 4,659 bytes in 8 blocks
==10092==    indirectly lost: 82 bytes in 5 blocks
==10092==      possibly lost: 0 bytes in 0 blocks
==10092==    still reachable: 173,777 bytes in 60 blocks
==10092==         suppressed: 0 bytes in 0 blocks
==10092== Rerun with --leak-check=full to see details of leaked memory
==10092==
==10092== For counts of detected and suppressed errors, rerun with: -v
==10092== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Elronnd
źródło
1
Nie przeciekasz pamięci; gccjest. Powinno to również działać z pustym programem. Spróbuj gcc src.c && valgrind ./a.out, co powinno dać czysty wynik.
3

C #, 109 bajtów

public class P{static void Main({for(;;)System.Xml.Serialization.XmlSerializer.FromTypes(new[]{typeof(P)});}}

Znaleźliśmy ideę tego wycieku w kodzie produkcyjnym i badanie tego prowadzi do tego artykułu. Głównym problemem jest długi cytat z artykułu (przeczytaj go, aby uzyskać więcej informacji):

Szukając PurchaseOrder dla mojego kodu , znajduję ten wiersz kodu na page_loadjednej z moich stronXmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder), new XmlRootAttribute(“”));

Wydaje się, że to całkiem niewinny fragment kodu. Tworzymy XMLSerializerdla PurchaseOrder. Ale co dzieje się pod przykryciem?

Jeśli spojrzymy na XmlSerializerkonstruktor z Reflector, stwierdzimy, że wywołuje on, this.tempAssembly = XmlSerializer.GenerateTempAssembly(this.mapping, type, defaultNamespace, location, evidence);który generuje zestaw temp (dynamiczny). Tak więc za każdym razem, gdy ten kod zostanie uruchomiony (tj. Za każdym razem, gdy strona zostanie trafiona), wygeneruje nowy zestaw.

Powodem, dla którego generuje zestaw, jest to, że musi on generować funkcje do serializacji i deserializacji, a te muszą gdzieś znajdować się.

Ok, w porządku… tworzy zespół, więc co? Kiedy to skończymy, powinno po prostu zniknąć, prawda?

Cóż… zestaw nie jest obiektem na GC Heap, GC jest naprawdę nieświadomy zespołów, więc nie będzie zbierał śmieci. Jedynym sposobem na pozbycie się zestawów w wersjach 1.0 i 1.1 jest zwolnienie domeny aplikacji, w której się ona znajduje.

I na tym polega problem Dr Watson.

Uruchomienie z kompilatora w Visual Studio 2015 i użycie okna narzędzi diagnostycznych pokazuje następujące wyniki po około 38 sekundach. Zauważ, że pamięć procesu stale się powiększa, a Garbage Collector (GC) działa nadal, ale nie może niczego zbierać.

Okno narzędzi diagnostycznych

TheLethalCoder
źródło
2

C 30 bajtów

f(){int *i=malloc(sizeof(4));}

Wyniki Valgrind:

         ==26311== HEAP SUMMARY:
         ==26311==     in use at exit: 4 bytes in 1 blocks
         ==26311==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated
         ==26311== 
         ==26311== LEAK SUMMARY:
         ==26311==    definitely lost: 4 bytes in 1 blocks
         ==26311==    indirectly lost: 0 bytes in 0 blocks
         ==26311==      possibly lost: 0 bytes in 0 blocks
         ==26311==    still reachable: 0 bytes in 0 blocks
         ==26311==         suppressed: 0 bytes in 0 blocks
         ==26311== Rerun with --leak-check=full to see details of leaked memory
Abel Tom
źródło
2
Czy zamiast tego można po prostu to zrobić main(){malloc(1);}?
kirbyfan64sos
@Tak to jest! Ale to już zostało opublikowane!
Abel Tom
2

Dart, 76 bajtów

import'dart:async';main()=>new Stream.periodic(Duration.ZERO).listen((_){});

Trochę jak odpowiedź JavaScript. Kiedy wywołujesz .listenobiekt strumienia Dart, otrzymujesz zwrot StreamSubscription, który pozwala ci rozłączyć się ze strumieniem. Jednak jeśli go wyrzucisz, nigdy nie możesz anulować subskrypcji strumienia, co spowoduje wyciek. Jedynym sposobem na usunięcie wycieku jest zebranie samego strumienia, ale nadal jest on wewnętrznie wywoływany przez kombinację StreamController + Timer.

Niestety, Dart jest zbyt mądry na inne rzeczy, których próbowałem. ()async=>await new Completer().futurenie działa, ponieważ użycie funkcji Oczekiwanie jest tym samym, co czynność new Completer().future.then(<continuation>), co pozwala zniszczyć samo zamknięcie, a do drugiego Completera nie ma odniesienia (Completer zawiera odniesienie do Przyszłości od .future, Future zawiera odniesienie do kontynuacji jako zamknięcie).

Ponadto izolaty (znane również jako wątki) są usuwane przez GC, więc odrodzenie się w nowym wątku i natychmiastowe wstrzymanie go ( import'dart:isolate';main(_)=>Isolate.spawn(main,0,paused:true);) nie działa. Nawet spawnowanie Izolatu z nieskończoną pętlą ( import'dart:isolate';f(_){while(true){print('x');}}main()=>Isolate.spawn(f,0);) zabija Izolację i wychodzi z programu.

No cóż.

Dwayne Slater
źródło
jeśli twój główny program nadal działał i robił inne rzeczy, czy śmieciarz kiedykolwiek byłby w stanie zatrzymać izolację? pytam, ponieważ mój przykład goroutine brzmi podobnie ... założyłem, że fakt, że program kończy działanie i oddaje całą pamięć systemowi operacyjnemu, niekoniecznie oznacza, że ​​nie wyciekł.
don bright
2

Szybki, 12 bajtów

[3,5][0...0]

Wyjaśnienie:

Jest to de facto wyciek pamięci, który może wystąpić w dowolnym języku, niezależnie od tego, czy język korzysta z ręcznego zarządzania pamięcią, automatycznego liczenia referencji (ARC, jak Swift), a nawet zamiatania śmieci.

[3,5]jest dosłownie tablicą. Ta tablica przydziela wystarczającą ilość pamięci dla co najmniej tych 2 elementów. 3I 5to tylko arbitralne.

Indeksowanie (indeksowanie) Array<T>tworzy ArraySlice<T>. An ArraySlice<T>jest widokiem pamięci Array, z której został utworzony.

[3,5][0...0]tworzy wartość ArraySlice<Int>, której wartość wynosi [3]. Zauważ, że 3w tym wycinku jest ten sam 3element, co 3w oryginale Arraypokazanym powyżej, a nie kopia.

Powstały wycinek można następnie zapisać w zmiennej i wykorzystać. Oryginalna tablica nie jest już przywoływana, więc można by pomyśleć, że można ją zwolnić. Jednak nie może.

Ponieważ wycinek odsłania widok pamięci tablicy, z której pochodzi, pierwotna tablica musi być utrzymywana przy życiu tak długo, jak plasterek żyje. Tak więc z pierwotnej 2wielkości pamięci o wielkości elementu, która została przydzielona, ​​używana jest tylko pamięć o wartości pierwszego rozmiaru elementu, przy czym drugi musi istnieć, aby nie przydzielić pierwszego. Rozmiar drugiego elementu pamięci jest przeciekany.

Rozwiązaniem tego problemu jest nie utrzymywanie przy życiu małych plasterków dużych tablic. Jeśli chcesz zachować zawartość wycinka, wypromuj ją w tablicy, która uruchomi pamięć do skopiowania, usuwając w ten sposób zależność od pamięci oryginalnej tablicy:

Array([3,5][0...0])
Alexander
źródło
2

Rozwiązanie 1: C (Mac OS X x86_64), 109 bajtów

Źródło dla golf_sol1.c

main[]={142510920,2336753547,3505849471,284148040,2370322315,2314740852,1351437506,1208291319,914962059,195};

Powyższy program należy skompilować z dostępem do wykonania w segmencie __DATA.

clang golf_sol1.c -o golf_sol1 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx

Następnie, aby uruchomić program, uruchom następujące czynności:

./golf_sol1 $(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')

Wyniki:

Niestety Valgrind nie szuka pamięci przydzielonej z wywołań systemowych, więc nie mogę pokazać ładnie wykrytego wycieku.

Możemy jednak spojrzeć na vmmap, aby zobaczyć dużą część przydzielonej pamięci (metadane MALLOC).

                                VIRTUAL   REGION 
REGION TYPE                        SIZE    COUNT (non-coalesced) 
===========                     =======  ======= 
Kernel Alloc Once                    4K        2 
MALLOC guard page                   16K        4 
MALLOC metadata                   16.2M        7 
MALLOC_SMALL                      8192K        2         see MALLOC ZONE table below
MALLOC_TINY                       1024K        2         see MALLOC ZONE table below
STACK GUARD                       56.0M        2 
Stack                             8192K        3 
VM_ALLOCATE (reserved)             520K        3         reserved VM address space (unallocated)
__DATA                             684K       42 
__LINKEDIT                        70.8M        4 
__TEXT                            5960K       44 
shared memory                        8K        3 
===========                     =======  ======= 
TOTAL                            167.0M      106 
TOTAL, minus reserved VM space   166.5M      106 

Wyjaśnienie

Myślę więc, że muszę opisać, co się tutaj właściwie dzieje, zanim przejdę do ulepszonego rozwiązania.

Ta główna funkcja nadużywa deklaracji brakującego typu C (więc domyślnie int, bez marnowania znaków podczas pisania), a także jak działają symbole. Linker dba tylko o to, czy nie może znaleźć symbolu, maindo którego należy zadzwonić. Więc tutaj tworzymy główną tablicę liczb całkowitych, które inicjujemy za pomocą naszego kodu powłoki, który zostanie wykonany. Z tego powodu main nie zostanie dodany do segmentu __TEXT, ale raczej do segmentu __DATA, dlatego musimy skompilować program z wykonywalnym segmentem __DATA.

Znaleziony w main kod powłoki jest następujący:

movq 8(%rsi), %rdi
movl (%rdi), %eax
movq 4(%rdi), %rdi
notl %eax
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret

Wywołuje to funkcję syscall w celu przydzielenia strony pamięci (syscall mach_vm_allocate używa wewnętrznie). RAX powinien być równy 0x100000a (mówi syscall, której funkcji chcemy), podczas gdy RDI utrzymuje cel alokacji (w naszym przypadku chcemy, aby to był mach_task_self ()), RSI powinien przechowywać adres, aby zapisać wskaźnik do nowo utworzonej pamięci (więc wskazujemy tylko sekcję na stosie), RDX zachowuje rozmiar alokacji (przekazujemy tylko RAX lub 0x100000a, aby zaoszczędzić na bajtach), R10 przechowuje flagi (wskazujemy, że może być przydzielone w dowolnym miejscu).

Teraz nie jest oczywiste, skąd RAX i RDI czerpią swoje wartości. Wiemy, że RAX musi mieć wartość 0x100000a, a RDI musi być wartością zwracaną przez mach_task_self (). Na szczęście mach_task_self () jest w rzeczywistości makrem zmiennej (mach_task_self_), która ma ten sam adres pamięci za każdym razem (jednak powinna się zmienić przy ponownym uruchomieniu). W moim szczególnym przypadku mach_task_self_ znajduje się pod adresem 0x00007fff7d578244. Aby ograniczyć instrukcje, zamiast tego przekażemy te dane z argv. Dlatego uruchamiamy program z tym wyrażeniem$(ruby -e 'puts "\xf5\xff\xff\xfe\xff\xff\x44\x82\x57\x7d\xff\x7f"')za pierwszy argument. Łańcuch to dwie połączone wartości, w których wartość RAX (0x100000a) wynosi tylko 32 bity i zastosowano do niej dopełnienie jednego (więc nie ma bajtów zerowych; po prostu NIE mamy wartości, aby uzyskać oryginał), następna wartość to RDI (0x00007fff7d578244), który został przesunięty w lewo z 2 dodatkowymi śmieciami dodanymi na końcu (ponownie, aby wykluczyć bajty puste, po prostu przesuwamy go z powrotem w prawo, aby przywrócić go do pierwotnego).

Po syscall piszemy do naszej nowo przydzielonej pamięci. Powodem tego jest to, że pamięć przydzielana za pomocą mach_vm_allocate (lub tego syscall) to w rzeczywistości strony VM i nie są automatycznie przenoszone do pamięci. Są one raczej zarezerwowane, dopóki dane nie zostaną do nich zapisane, a następnie strony te zostaną zmapowane w pamięci. Nie byłem pewien, czy spełniłby wymagania, gdyby był zarezerwowany.

W kolejnym rozwiązaniu wykorzystamy fakt, że nasz kod powłoki nie ma zerowych bajtów, więc możemy przenieść go poza kod naszego programu, aby zmniejszyć rozmiar.

Rozwiązanie 2: C (Mac OS X x86_64), 44 bajty

Źródło dla golf_sol2.c

main[]={141986632,10937,1032669184,2,42227};

Powyższy program należy skompilować z dostępem do wykonania w segmencie __DATA.

clang golf_sol2.c -o golf_sol2 -Xlinker -segprot -Xlinker __DATA -Xlinker rwx -Xlinker rwx

Następnie, aby uruchomić program, uruchom następujące czynności:

./golf_sol2 $(ruby -e 'puts "\xb8\xf5\xff\xff\xfe\xf7\xd0\x48\xbf\xff\xff\x44\x82\x57\x7d\xff\x7f\x48\xc1\xef\x10\x8b\x3f\x48\x8d\x74\x24\xf8\x89\xc2\x4c\x8d\x50\xf7\x0f\x05\x48\x8b\x36\x89\x36\xc3"')

Wynik powinien być taki sam jak poprzednio, ponieważ dokonujemy alokacji o tym samym rozmiarze.

Wyjaśnienie

Stosuje tę samą koncepcję, co rozwiązanie 1, z tym wyjątkiem, że przenieśliśmy część naszego wycieku kodu poza program.

Kod powłoki znaleziony w main jest teraz następujący:

movq 8(%rsi), %rsi
movl $42, %ecx
leaq 2(%rip), %rdi
rep movsb (%rsi), (%rdi)

To w zasadzie kopiuje kod powłoki, który przekazujemy w argv po tym kodzie (więc po skopiowaniu go uruchomi wstawiony kod powłoki). Na naszą korzyść działa to, że segment __DATA będzie miał co najmniej rozmiar strony, więc nawet jeśli nasz kod nie jest tak duży, nadal możemy „bezpiecznie” pisać więcej. Minusem jest tutaj idealne rozwiązanie, nawet nie potrzebuje kopii, zamiast tego po prostu wywołuje i wykonuje kod powłoki bezpośrednio w argv. Ale niestety ta pamięć nie ma uprawnień do wykonywania. Moglibyśmy zmienić prawa do tej pamięci, jednak wymagałoby to więcej kodu niż zwykłe kopiowanie. Alternatywną strategią byłaby zmiana praw z programu zewnętrznego (ale o tym później).

Kod powłoki, który przekazujemy argv, jest następujący:

movl $0xfefffff5, %eax
notl %eax
movq $0x7fff7d578244ffff, %rdi
shrq $16, %rdi
movl (%rdi), %edi
leaq -0x8(%rsp), %rsi
movl %eax, %edx
leaq -9(%rax), %r10
syscall
movq (%rsi), %rsi
movl %esi, (%rsi)
ret

Jest to bardzo podobne do naszego poprzedniego kodu, z tą różnicą, że uwzględniamy bezpośrednio wartości EAX i RDI.

Możliwe rozwiązanie 1: C (Mac OS X x86_64), 11 bajtów

Pomysł zmodyfikowania programu zewnętrznie daje nam możliwe rozwiązanie przeniesienia przecieku do programu zewnętrznego. Tam, gdzie nasz rzeczywisty program (przesłanie) jest tylko programem zastępczym, a program nieszczelny przydzieli część pamięci w naszym programie docelowym. Teraz nie byłem pewien, czy będzie to zgodne z zasadami tego wyzwania, ale mimo to podzielam się nim.

Więc jeśli użyjemy mach_vm_allocate w zewnętrznym programie z celem ustawionym na nasz program wyzwań, może to oznaczać, że nasz program wyzwań musiałby być czymś w rodzaju:

main=65259;

Tam, gdzie ten kod powłoki to po prostu krótki skok do siebie (nieskończony skok / pętla), więc program pozostaje otwarty i możemy odwoływać się do niego z zewnętrznego programu.

Możliwe rozwiązanie 2: C (Mac OS X x86_64), 8 bajtów

Co zabawne, kiedy patrzyłem na produkcję valgrind, zobaczyłem, że przynajmniej według valgrind, słaba pamięć przecieków. Tak skutecznie każdy program przecieka trochę pamięci. Mając to na uwadze, moglibyśmy po prostu stworzyć program, który nic nie robi (po prostu wychodzi) i faktycznie wycieknie pamięć.

Źródło:

main(){}


==55263== LEAK SUMMARY:
==55263==    definitely lost: 696 bytes in 17 blocks
==55263==    indirectly lost: 17,722 bytes in 128 blocks
==55263==      possibly lost: 0 bytes in 0 blocks
==55263==    still reachable: 0 bytes in 0 blocks
==55263==         suppressed: 16,316 bytes in 272 blocks
ScrimpyCat
źródło
2

Zwykły angielski , 71 70 58 35 bajtów

Usunięto 1 bajt, usuwając pusty wiersz. Usunięto 12 bajtów poprzez wyeliminowanie definicji typu „bogon” i użycie nadrzędnego typu „rzecz” zamiast podtypu „bogon”. Usunięto 23 bajty, przechodząc z kompletnego programu do zwykłej procedury, która przecieka pamięć.

Wersja golfowa:

To x:
Allocate memory for a thing.

Wersja bez golfa, która jest kompletnym programem, używa definicji podtypu i nie przecieka pamięci:

A bogon is a thing.

To do something:
  Allocate memory for a bogon.
  Destroy the bogon.

To run:
  Start up.
  Do something.
  Shut down.

Jeśli zostanie wywołana wersja „x” gry w golfa, nastąpi wyciek pamięci proporcjonalnie do liczby wywołań „x”. W wersji golfowej „Cofnij przydzielenie rzeczy”. naprawiłby wyciek pamięci.

Zwykły angielski domyślnie sprawdza, czy nie ma wycieków pamięci. Po uruchomieniu wersji, która przecieka pamięć, tuż przed zamknięciem programu pojawi się okno dialogowe. Okno dialogowe ma tytuł „debugowanie”, komunikat „1 kroplówka” i przycisk „OK”. Im więcej razy wywoływana jest funkcja przecieku, tym większa liczba „kropelek” w komunikacie. Po uruchomieniu wersji, która nie przecieka pamięci, okno dialogowe nie pojawia się.

W zwykłym języku angielskim „rzecz” to wskaźnik do pozycji na liście podwójnie połączonej. „Rzecz”, „uruchomić” i „zamknąć” są zdefiniowane w module o nazwie „makaron”, który należy skopiować (zwykle jako osobny plik) do każdego projektu. „A”, „the”, „to”, „przydzielić pamięć” i „zniszczyć” są zdefiniowane w kompilatorze.

Jaspis
źródło