Programozgatás & AI

Szembejött a minap egy egyszerűnek látszó hekkelési probléma. Beágyazott program, RISC architekúra (konkrétan MSP430X), volt benne egy efféle

if ( feltetel )
 {      a=csinalj(ezzel,0);
        b=meg_ezzel();
        valamit(a);
        es_megvalamit(b);
 }

rész, és ezt kellett kiiktatni úgy hogy a binary image-be kellene (távolról) beleírni ezt-azt hogy ugorja át ezt az egész blokkot. Sajnos a "feltetel" nem volt azonosan 0-ra írható sehogy (saját design flaw, lásd még: aki hülye, nem kap fagyit), így maradt az hogy egy jól irányzott "jmp" beszúrása az "a=csinalj(ezzel,0)" helyének elejére, ami az es_megvalamit(b); után ugrik. Mindezt úgy hogy a binary image ne módosuljon (azaz ne csússzon el) és lehetőleg csak ott a blokk elején kelljen pár byte-ot átirni.

A a=csinalj(ezzel,0); rész az egy jmp + néhany nop-pal lefedhető, így lett egy ilyen rész:

#define really  0

if ( feltetel )
 {      if ( really )
         {      a=csinalj(ezzel,0);
         }
        else
         {      asm volatile ("jmp itt_a_vege   \r\n");
                asm volatile ("nop              \r\n");
                /* ... */
                asm volatile ("nop              \r\n");
         }
        b=meg_ezzel();
        valamit(a);
        es_megvalamit(b);

        itt_a_vege:
        (void)0;
 }

Ugy hogy annyi nop legyen hogy tényleg ne csússzon szét az image. A paraméterek (ezzel, 0) ABI-szerinti átadása, függvényhívás, return value lementése tényelg jóval összetettem mint egy jmp, így kell az a néhány nop.  Ez a fenti így persze nem fordult le, és a C <-> assembly <-> inline assembly <-> C interfacing, ha nem használjuk napi szinten akkor tud fekete mágiának kinézni, finom részletekre nem mindig emlékszem kapásból plusz ilyet még eleve sosem csináltam, úgyhogy... úgyhogy gondoltam miért ne kérdezzük meg az információtól AI-tól hogy mi a teendő. How can we jump to a label from inline assembly using GCC. Egyenes kérdés, a válasz is egyenes. Próbának jó lesz. 

A ChatGPT ezt mondta:

         main() {
    void *label_ptr;

    // Take address of the label
    label_ptr = &&my_label;

    // Inline assembly jumps to label_ptr
    asm volatile (
        "jmp *%0"
        :
        : "r" (label_ptr)
    );

    // This won't be reached due to jump
    printf("This will be skipped.\n");

my_label:
    printf("Jumped to label!\n");
    return 0;

Mindezt tökéletes magabiztonsággal ("Correct Way (Using labels as values + inline assembly)"). 

A valóság azzal szemben az hogy ezt vagy úgy lehet megcsinálni, hogy a label-t nem C szerint hanem inline assembly szerint kell definiálni:

asm volatile ("itt_a_vege: \r\n");

vagy asm goto-val, jmp %l[label], stb. Mindkettő okés. Itt most az egyszerűbb de kevésbé ajánlott ment élesben, egyszeri hekkelésre - főleg a disassembly/binary image átnézése után - jó ez. 

Tanulságok: pro:

  • Felhívta a figyelmemet arra hogy ez veszélyes, nonstandard, architektúra-függő, stb. 
  • Ezt a label_ptr = &&my_label szintaxist nem ismertem, lásd még: TIL. Később még jól jöhet ha az ember redukálni akarja az (inline) assembly-t erősen low level kódokban (low latency interrupt handlerek, context switching, bármi hasonló). 
  • A kód legalább le sem fordul, mintsemhogy elkezdene hülyeségeket csinálni. 

Kontra:

  •  Magabiztosan nem működő kód, pedig ez egy klasszik minimal working example. 
  •  Nyelvkeverés magas szinten: "jmp *%0", ez eléggé wtf.
  •  Nyelvi elemek rossz használata: a label az nem void * hanem void(*)(void) tipusú, bár ez a legkevesebb (és hát valljuk be, néha előfordul hogy kellhet ilyet csinálni).

Meg persze ha lenne idő(m), megkérdeznék mást is, opcionálisan előfizetésest... hátha valamelyik megmondja a jó választ.