UB, hacki, dokładna specyfikacja - czy to w Delphi istnieje?

UB, hacki, dokładna specyfikacja - czy to w Delphi istnieje?
AL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1493
0

Idąc za ciosem:
wkleiłem to dziś koło południa: Programistyczne WTF jakie Was spotkały
Okazuje się, że toto cudo techniki wynika z wymogów API. Generalnie kwiat inżynierii kodu znaleziony został w pluginie wczytywanym przez appkę napisaną w Delphi 7.
Ona takiego czegoś oczekuje. W praktyce: do pluginu powinna "wchodzić" tablica (wskaźnk) struktur (pascalowy record po stronie Delphi), plugin ją wypełnia, appka robi sobie z nią dalej co tam chce. No ale nie, appka oczekuje, że zamiast iterowania jak cywilizowany człowiek, będzie przesuwać się o pole len czy inny size w tymże rekordzie/strukturze, (bo optymalizacja miejsca jest ważna. Zwłaszcza pojedynczych bajtów w dobie wielu gigabajtów ramu ;)). Chciałbym to fixnąć, bo doświadczenie podpowiada mi, że to jest błędogenne jak diabli.
Można to połatać na etapie plugina ileś typów wiadomości, odpowiednio potem castować ale to rzeźba ohydna.

I teraz moje pytanie brzmi: w C/C++ takie akcje to jest ewidentny UB. Czy w Pascalu można sobie radośnie tak jeździć po pamięci, czy to po prostu wieloletni fart, że działa? Google nie daje jednoznacznych odpowiedzi, ew. nie do końca wiem gdzie szukać.

Fundamentalne kwestia jest taka: czy to w ogóle jest poprawne, jeżeli Size <> SizeOf(Msg)
Msg := Pointer(LongInt(Msg) + Size); ?

Odpowiednik tego potworka w C++ wyglądałby

Kopiuj

struct Header
{ 
  int field1;
  int field2;
  int size;
};
struct Msg
{
  struct Header header;
  char payload[0xffff];
};
size_t payload_size = 8;
//dwie wiadomosci
void* buffer = malloc(2* (sizeof(struct Header) + payload_size));
struct Message* msg= buffer;
msg->header.size = payload_size;
msg->payload[0]= 'x';
/... ciach ...
msg->payload[7]= 'z';
sendMsg(msg);
msg = reinterpret_cast<struct Message*>(
   reinterpret_cast<int>(msg) + sizeof(struct Header) + payload_size); 

msg->header.size = payload_size;
//analogicznie jak wcześniej
xxx_xx_x
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 365
0

Częstą praktyką gdy mapujesz bufor danych na strukturę.

AL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1493
0

Problemem nie jest fakt mapowania bufora na strukturę.
Potencjlanym problemem jest fakt, że ta struktura nie jest nawet w pełni doalokowana. Masz kilka bajtów nagłówka (zawierającego długość payloadu) i zafiksowanej długości tablicę payload. Alokujesz bufor o długości nagłówek + fragment payloadu, potem castujesz strukturę (z całym payloadem) na to. I pytanie czy jest to poprawne?

xxx_xx_x
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 365
0

Jak byś chciał rozwiązać dynamiczna alokację tablicy payload bez tego triku z zachowaniem ciągłości struktury i z możliwością zapisu/odczytu przez tablice?

AL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1493
0

Payload NIE jest dynamicznie alokowany.

Kopiuj

type  PMessage           = ^TMessage;                 
      TMessage           = packed record            
      Field1 : Integer;               
      Field2 : Integer;                 
      Size   : Integer;             
      Payload : array[0..65535] of Byte;   
      end;

Natomiast bufor pod spodem ma np. 128 bajtów., czyli w payload zmieści się 128 - 3*(SizeOfInteger).
Pytam czy sam taki cast jest poprawny, bo w takim C/C++ (w którym piszę więcej) jest on "nielegalny".

xxx_xx_x
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 365
0

Wszystko zależy co rozumiesz przez poprawny. Zauważ że w twoim rozwiązaniu dane są inaczej upakowane każda wiadomość będzie się składać z header + 64k danych.
Co w sytuacji gdy korzystasz z biblioteki, która wymaga gęstego upakowania albo np dostajesz stream bajtow z socketu w formie (header + payload) i chcesz je odpakować?
Ten trik pozwala rozwiązać ten problem

Taki sam trik np stosował Quake3

https://github.com/id-Softwar[...]Arena/blob/master/q3map/vis.h

Kopiuj
typedef struct
{
    int     numpoints;
    vec3_t  points[MAX_POINTS_ON_FIXED_WINDING];            // variable sized
} winding_t;
KA
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Gorlice
xxx_xx_x
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 365
0

@kAzek: tu bardziej chodzi o teoretyzowanie czy takie rozwiazanie jest ok, jak dla mnie to zależy od sytuacji. Na pewno trzeba miec sensowne uzasadnienie żeby użyć tego podejścia.

Kopiuj
#include <stdio.h>
#include <stdlib.h>

#pragma pack(push, 1)
typedef struct {
    char length;
    char payload[65536];
} Msg;

#pragma pack(pop)

int main()
{
    char *ptr = (char*)malloc(sizeof(char) * 2 + 8 * sizeof(char));
    
    Msg *msg1 = (Msg*)ptr;              // message (header + payload size 3)
    Msg *msg2 = (Msg*)(ptr + 1 + 3);
    
    msg1->length = '3';
    msg1->payload[0] = 'a';
    msg1->payload[1] = 'b';
    msg1->payload[2] = 'c';
    
    msg2->length = '5';
    msg2->payload[0] = 'd';
    msg2->payload[1] = 'e';
    msg2->payload[2] = 'f';
    msg2->payload[3] = 'g';
    msg2->payload[4] = 'h';
    
    for(int i=0; i<10; i++) {
        printf("%c", ptr[i]);
    }
    printf("\n");

    free(ptr);
    return 0;
}

Daje jeden stream bajtów :
3abc5defgh

https://en.wikipedia.org/wiki/Flexible_array_member

flowCRANE
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Tuchów
  • Postów: 12269
1
alagner napisał(a):

Ona takiego czegoś oczekuje. W praktyce: do pluginu powinna "wchodzić" tablica (wskaźnk) struktur (pascalowy record po stronie Delphi), plugin ją wypełnia, appka robi sobie z nią dalej co tam chce. No ale nie, appka oczekuje, że zamiast iterowania jak cywilizowany człowiek, będzie przesuwać się o pole len czy inny size w tymże rekordzie/strukturze, (bo optymalizacja miejsca jest ważna. Zwłaszcza pojedynczych bajtów w dobie wielu gigabajtów ramu ;)).

Późno już i zmęczonym, ale z tego co rozumiem, zamiast tablicy struktur przekazywany jest wskaźnik i zamiast przesuwać jego adres o SizeOf(TypStruktury), iteruje się o liczbę bajtów z jakiegoś tam Size? Jeśli tak, to coż… ktoś chciał po prostu zaoszczędzić pamieć. :)

Chciałbym to fixnąć, bo doświadczenie podpowiada mi, że to jest błędogenne jak diabli.

Nie tyle jest błędogenne, co może takie być – poprawnie napisany kod, biorący pod uwagę wszystkie przypadki brzegowe będzie działał prawidłowo, nie ma tutaj żadnych cudów.

I teraz moje pytanie brzmi: w C/C++ takie akcje to jest ewidentny UB. Czy w Pascalu można sobie radośnie tak jeździć po pamięci, czy to po prostu wieloletni fart, że działa?

W Pascalu nie ma czegoś takiego jak UB – albo dana konstrukcja jest dozwolona, albo poleci wyjątek.

Fundamentalne kwestia jest taka: czy to w ogóle jest poprawne, jeżeli Size <> SizeOf(Msg)
Msg := Pointer(LongInt(Msg) + Size); ?

Pewnie, bo dlaczego miało by być inaczej? Wskaźnika (w tym typowanego) można używać do dostępu do jakichkolwiek danych w jakimkolwiek buforze – w końcu bufor to tylko ciąg bajtów. Ważne jest jednak to, aby prawidłowo określić adres, użyć odpowiednich typów danych i wykonać prawidłowe rzutowanie.

To daje masę możliwości, jednak jeśli już ktoś decyduje się na radosne śmiganie pointerem po pamięci, to kod należy napisać uważnie i go porządnie przetestować. Choć z tym LongIntem bym uważał – IMO odpowiedni byłby tutaj PtrInt.

AL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1493
0
furious programming napisał(a):

Późno już i zmęczonym, ale z tego co rozumiem, zamiast tablicy struktur przekazywany jest wskaźnik i zamiast przesuwać jego adres o SizeOf(TypStruktury), iteruje się o liczbę bajtów z jakiegoś tam Size? Jeśli tak, to coż… ktoś chciał po prostu zaoszczędzić pamieć. :)

Zauważyłem :>

furious programming napisał(a):>

Nie tyle jest błędogenne, co może takie być – poprawnie napisany kod, biorący pod uwagę wszystkie przypadki brzegowe będzie działał prawidłowo, nie ma tutaj żadnych cudów.

W C i C++ (z których się wywodzę) takowe (cuda) mogą być, taka jest geneza mojego pytania. Pascala znam na poziomie "umiem się po nim poruszać" ale już takich niuansów nie jestem do końca świadom.

furious programming napisał(a):>

W Pascalu nie ma czegoś takiego jak UB – albo dana konstrukcja jest dozwolona, albo poleci wyjątek.

No i to w zasadzie zamyka temat. Podziękował.

Jeśli mogę jeszcze zadać pytanie pomocnicze, lekko poza tematem:
ile toto jest warte? https://github.com/pleriche/FastMM4

WL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1084
1
alagner napisał(a):
furious programming napisał(a):

Późno już i zmęczonym, ale z tego co rozumiem, zamiast tablicy struktur przekazywany jest wskaźnik i zamiast przesuwać jego adres o SizeOf(TypStruktury), iteruje się o liczbę bajtów z jakiegoś tam Size? Jeśli tak, to coż… ktoś chciał po prostu zaoszczędzić pamieć. :)

Zauważyłem :>

furious programming napisał(a):>

Nie tyle jest błędogenne, co może takie być – poprawnie napisany kod, biorący pod uwagę wszystkie przypadki brzegowe będzie działał prawidłowo, nie ma tutaj żadnych cudów.

W C i C++ (z których się wywodzę) takowe (cuda) mogą być, taka jest geneza mojego pytania. Pascala znam na poziomie "umiem się po nim poruszać" ale już takich niuansów nie jestem do końca świadom.

furious programming napisał(a):>

W Pascalu nie ma czegoś takiego jak UB – albo dana konstrukcja jest dozwolona, albo poleci wyjątek.

No i to w zasadzie zamyka temat. Podziękował.

W Pascalu, jak i w każdym (chyba?) non-managed, można wszystko.
Pod warunkiem, że się wie co i jak.
Jak ktoś ma potrzebę/lubi to polecam chociażby to:
https://github.com/MahdiSafsafi/DDetours

Albo inaczej - wyłącz sobie range-checking w kompilatorze, przepełnij dozwolony zakres zmiennej lub tablicy i będziesz miał UB w Pascalu, bez wyjątku :D
Ale fakt, dużo jest trzymane za pysk.

Jeśli mogę jeszcze zadać pytanie pomocnicze, lekko poza tematem:
ile toto jest warte? https://github.com/pleriche/FastMM4

To zależy do czego.
Sam poczytaj:
http://blog.synopse.info/post/2010/12/04/SynScaleMM
http://blog.synopse.info/post/2013/12/05/New-Open-Source-Multi-Thread-ready-Memory-Manager%3A-SAPMM

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.