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.
 

Hozzászólások

Nekem van előfizetésem. Megkérdezem neked ha akarod.

Még nincs aláírásom.

Itt jön a válasz...

🕙 2025-08-23 16:04

Two portable-ish GNU C ways:

1) asm goto (best if your GCC supports it)

asm goto lets inline assembly branch to C labels directly.

#include <stdio.h>

void demo(int x) {
    asm goto(
        "test %0, %0\n\t"
        "jz %l[is_zero]\n\t"
        :
        : "r"(x)          // inputs
        :                  // clobbers (add "memory" if needed)
        : is_zero          // C label(s) you may jump to
    );
    puts("non-zero");
    return;

is_zero:
    puts("zero");
}

Notes:

  • Use %l[labelname] (or %l0 etc.) in the asm template to reference a C label from the final : ... : labels list.

  • Add "memory" to clobbers if the asm influences memory-observable state.

  • Supported by GCC 4.5+; Clang supports it only in newer versions (and not on all targets).

2) “Labels as values” + indirect jump (fallback)

If you can’t use asm goto, take the address of a C label (GNU C extension) and jump to it indirectly:

#include <stdio.h>

void demo(int x) {
    void *target = &&nonzero;
    if (x == 0) target = &&zero;

    // Jump via register operand
    __asm__ volatile ("jmp *%0" :: "r"(target));

zero:
    puts("zero");
    return;

nonzero:
    puts("non-zero");
}

Notes:

  • This uses GNU C’s computed goto (&&label) plus an indirect jmp *reg.

  • Works in C (GNU C); not standard C/C++.

  • The inline asm sees the label address as a normal pointer operand.

Which should I use?

  • Prefer asm goto when available: it’s designed for exactly this use case and integrates with the optimizer/control flow.

  • Use the computed-goto + indirect jmp trick if asm goto isn’t available on your compiler/target.

If you share your exact target (arch/OS) and GCC/Clang versions, I can tailor the snippet (e.g., different jump mnemonics, clobbers, or constraints).

Még nincs aláírásom.

A válasz elején szerepelt egy ilyen szöveg "Thought for 14s", ami kinyitható (chatGPT5 modell):

Kibontva ez van benne:

When jumping to C labels from inline assembly in GCC, you can use asm goto or computed gotos. The correct syntax for asm goto is:

asm goto ("jmp %l0" ::: : target);

You'll also need to specify inputs, clobbers, and the target labels. For example, you could use:


 

asm goto ("test %0, %0\n\t jz %l[iszero]" : : "r"(x) : : iszero);

In contrast, x86 doesn't allow direct jumps from inline asm to C labels without asm goto.

In GCC, you can't use a direct jump like jmp some_c_label unless you use local labels with &&label. This technique allows indirect jumps via registers:


 

register void *p = &&label; asm volatile("jmp *%0"::"r"(p));

This works in GNU C, but not C++. Inline assembly can't directly refer to C labels unless using asm goto or computed gotos. Clang doesn't support asm goto until GCC 4.5.

The user initially asked for timestamps and had a system that remembered this through updates, but they've reset their context a few times since then. They haven't asked for a timestamp recently, but to be safe, I’ll continue including it unless they explicitly tell me otherwise.

Még nincs aláírásom.

Koszi!

Kozben vegigcsamcsogtam az architekturakat es ugy nezem hogy ez a *%0 megoldas csak x86-oson megy. Az osszes tobbin amit neztem, nem, pedig ugye direkt megfeleloje, ha ugy vesszuk van (ARM-ben, RISC-V-ban es MSP430(X)-ben biztos, minden regiszterrel, AVR-nel limitaltan, csak Z-re). A labeles pelda az szepen megy mindenhol (persze a test/jz-ket megfeleloen at kell irni, de ugye az nem gond). Akkor a fizetos valtozat tobbmindent megtalal.

Clang doesn't support asm goto until GCC 4.5.

Hm... ez is egy erdekesen sikeredett mondat lett... 

Koszi!

De igen, erdekesek a tanulsagok: az ingyenes megtalalta az egyik, de sajna (valami miatt) x86-specifikus megoldast. A fizetos megtalalta az altalanos masik megoldast, az mukodik mindenhol tok jol mint minimal working example. Az altalanos harmadikat meg a fizetos sem talalta meg. 

> Sajnos a "feltetel" nem volt azonosan 0-ra írható sehogy

Valamit csinálsz a feltételben? Úgy értem, hogy

if (somefunction())

ahol a somefunction csinál valamit, aminek mindenképp meg kell történnie, és a visszatérési érték alapján történik a döntés? Vagy én se kapok fagyit? :))

Debian - The "What?!" starts not!
http://nyizsa.blogspot.com

Sajna #define feloldasbol jon, azonosan igaz. Mondom, aki hulye nem kap fagyit. Nem is kaptam :)

Egyebkent ez egy RTOS kernelhez periferia + halozati linterface inicializalas lenne (igy max routing szinten tiltom le ha nem akarom hasznalni, a #define csak az hogy a kernelhez ne forditsa hozza az aux libeket meg a KEEP-esen linkelt handlert felejtse el hogy kisebb legyen a forras). Az engineering modelben amin dolgoztam be volt forrasztva az illeszto csipp, az eles hardverben meg nem. Es keson szolta a cimbora  hogy bocsi, rosszul emlekezett, nem forrasztotta be. Na, erre nem voltam felkeszulve... 

Jo otlet lehet de igy egyreszt kicsit sok adatot kell feltolteni a joszagra a valtoztatas jellegehez kepest.

Masreszt, a nagyobbik gond, hogy ugy nem teljesen egyertelmu hogy meddig kell tolteni nop-okkal fel. Ezt ugyan a disasm tanulmanyozasaval megtehetjuk, de ugy is roppantul erteni kell a disasm alapjan hogy mi tortenik. Ennel a modszernel megvan az az elony hogy ugyanugy forditott C kod a peccselt valtozat es egyszeruen a hexdumpok diffjet toltlom fel. Igy ha barmi optimalizacio miatt elkezd keveredni a kod (ilyen out of order jelleggel, csak ugye pipeline stallokra optimalizalva) akkor az a diff-nel ez is kibukik. Igy legrosszabb esetben nemcsak az atugro utasitas videket kell megpeccselni hanem a label kornyeken is keletkezhet difi (nehany instruction-nyi), es igy az is megy fel.

Nekem itt az a feltetel nem stimmel, hogy miert akarsz mindent a helyen hagyni? Ha utana olyan valtoztatasra lesz igeny valahol, ami nem fer el a jelenlegi helyen, fogod, es jumpolsz a vegere, aztan vissza?

Azt meg megertem, ha valami fix dolgot a jelenlegi helyen szeretnel tartani, de ahhoz inkabb linkeres trukkozes kell.

Tipikusan a mikrokontroller egetese par masodperc - eleg hamar felmegy a teljes kod, es a flash mondjuk 10000* ujrairhato, ebbol meg fejlesztes kozben is nehez kifutni, hat meg production kutyuknel. Persze ha csak szakmai kivancsisag hajt, akkor hajra!

A strange game. The only winning move is not to play. How about a nice game of chess?

Itt most csak egy gyors hotfix volt hogy egy joliranyzott jmp ugorja at a kritikus reszt (konkretan az MCU ~tucatnyi hw inicializalasbol kellet az egyiket kiiktatni). Csak azert csinaltam igy hogy tenyleg csak a joliranyzott jmp-t injektalja bele a fordito a jo helyre, hogy pont oda ugorjon ahova kell (es ne nekem kelljen offset-et meg pc-alignmentet szamolni) es hogy biztos hogy atugorhato legyen (es/vagy kideruljon ha nem atugorhato, pl egy pipeline stall optimization miatt) ez az if ( ... ) blokk. Ezutobbiaknak volt az ara ez a ~feltucat nop... ez van :)

Persze, komolyabb fejlesztesnel rendes ujraforditas + ujrafeltoltes van, csak itt ennel az esetnel a savszelesseg meg a feltoltesi lehetoseg az nem eppen a legoptimalisabb. Igy ez a kis praktika/sporolas plusz a szakmai kivancsisag volt itt a fo cel.