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

UB, hacki, dokładna specyfikacja - czy to w Delphi istnieje?
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • 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:prawie 13 lat
  • Ostatnio:12 dni
  • Postów:365
0

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

AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • 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?

edytowany 1x, ostatnio: alagner
xxx_xx_x
  • Rejestracja:prawie 13 lat
  • Ostatnio:12 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:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • 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".

edytowany 2x, ostatnio: alagner
xxx_xx_x
  • Rejestracja:prawie 13 lat
  • Ostatnio:12 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;
edytowany 1x, ostatnio: xxx_xx_x
KA
  • Rejestracja:prawie 20 lat
  • Ostatnio:minuta
  • Lokalizacja:Gorlice
0

Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.
xxx_xx_x
  • Rejestracja:prawie 13 lat
  • Ostatnio:12 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

edytowany 2x, ostatnio: xxx_xx_x
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Tuchów
  • Postów:12164
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.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 3x, ostatnio: flowCRANE
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • 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

abrakadaber
abrakadaber
w nowszych delphi to domyślny menadżer pamięci a w D7 jest mocno polecane
WL
  • Rejestracja:około 21 lat
  • Ostatnio:około 2 miesiące
  • Postów:1082
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

edytowany 1x, ostatnio: wloochacz
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)