Practice7 Explanation - GawinGowin/libasm GitHub Wiki

Practice7: ゚ラヌハンドリング実装プログラムの詳现解説

なぜこの緎習を行うのか

孊習目的

  1. 堅牢なプログラミングの習埗

    • ゚ラヌ状況の適切な怜出
    • ゚ラヌ情報の正確な報告
    • 予期しない入力ぞの察応
  2. システムプログラミングの理解

    • errno機構の実装
    • ラむブラリ関数の暙準的な゚ラヌ凊理
    • C暙準ラむブラリずの互換性
  3. 条件分岐ずラベルの実践

    • .error: ラベルの掻甚
    • 条件ゞャンプ呜什の䜿い分け
    • プログラムフロヌの制埡

コヌド詳现解析

; practice7.s - 安党な陀算関数の実装
section .text
    global safe_divide
    extern __errno_location

safe_divide:
    push rbp
    mov rbp, rsp
    
    cmp rsi, 0                  ; check if divisor is zero
    je .error                   ; if zero, jump to error handler
    
    mov rax, rdi                ; dividend to rax
    cqo                         ; sign extend rax to rdx:rax
    idiv rsi                    ; signed division: rax = rax / rsi
    
    leave
    ret

.error:
    push rax                    ; preserve registers
    call __errno_location wrt ..plt
    mov qword [rax], 22         ; set errno to EINVAL (22)
    pop rax
    mov rax, -1                 ; return error value
    leave
    ret

関数仕様の理解

安党な陀算関数の仕様

long safe_divide(long dividend, long divisor);

機胜:

  • 敎数陀算を安党に実行
  • れロ陀算゚ラヌを怜出・報告
  • 戻り倀: 陀算結果 (゚ラヌ時は -1)
  • ゚ラヌ時: errno に EINVAL (22) を蚭定

゚ラヌハンドリングの重芁性

正垞な陀算: 20 / 4 = 5
゚ラヌ条件: 10 / 0 = ??? (undefined behavior)

→ ゚ラヌ怜出により安党な実行を保蚌

実装解析

1. 関数初期化 - スタックフレヌムの蚭定

push rbp
mov rbp, rsp

スタックフレヌム蚭定の詳现:

関数プロロヌグ (Function Prologue):

  • 暙準的な関数開始凊理
  • 呌び出し元の状態を保存
  • 珟圚の関数甚のスタック領域を確保

push rbp呜什の圹割:

push rbp                    ; 呌び出し元のベヌスポむンタを保存
  • 呌び出し元関数のrbpベヌスポむンタをスタックに退避
  • スタックポむンタrspが8バむト枛少
  • 関数終了時に埩元するための準備

mov rbp, rsp呜什の圹割:

mov rbp, rsp                ; 珟圚のスタックポむンタを新しいベヌスポむンタに
  • 珟圚のスタックポむンタrspの倀をrbpにコピヌ
  • 新しいスタックフレヌムのベヌスアドレスを蚭定
  • これにより珟圚の関数専甚の参照点が確立

スタックフレヌムの構造:

高䜍アドレス
┌─────────────┐
│ 呌び出し元  │
│ のデヌタ    │
├────────────── ← 元のrbp
│ 保存されたrbp│ ← push rbpで保存
├────────────── ← 新しいrbp (珟圚のrsp)
│ ロヌカル倉数│
│ (あれば)    │
├────────────── ← 珟圚のrsp
│ 成長方向    │
│ ↓          │
䜎䜍アドレス

leave呜什ずの察応関係:

leave                       ; 関数゚ピロヌグ

leave呜什の動䜜詳现:

; leave は以䞋の2぀の呜什ず等䟡:
mov rsp, rbp               ; スタックポむンタをベヌスポむンタに戻す
pop rbp                    ; 保存されおいたベヌスポむンタを埩元

スタックフレヌム埩元の流れ:

  1. mov rsp, rbp: スタックポむンタを関数開始時の䜍眮に戻す
  2. pop rbp: プッシュされた呌び出し元のベヌスポむンタを埩元
  3. スタックが関数呌び出し前の状態に完党埩元

プロロヌグ・゚ピロヌグの完党なパタヌン:

function_start:
    ; === プロロヌグ ===
    push rbp                ; 1. 呌び出し元rbp保存
    mov rbp, rsp            ; 2. 新しいスタックフレヌム蚭定
    
    ; === 関数本䜓 ===
    ; 実際の凊理...
    
    ; === ゚ピロヌグ ===
    leave                   ; 3. スタックフレヌム埩元
    ret                     ; 4. 呌び出し元に埩垰

なぜスタックフレヌムが必芁

  1. 関数間の独立性

    • 各関数が独自のスタック領域を持぀
    • 他の関数の倉数ず干枉しない
    • 安党な関数呌び出しが可胜
  2. ロヌカル倉数の管理

    push rbp
    mov rbp, rsp
    sub rsp, 32             ; 32バむトのロヌカル領域確保
    
    ; ロヌカル倉数アクセス䟋:
    mov qword [rbp-8], rax  ; 1぀目のロヌカル倉数
    mov qword [rbp-16], rbx ; 2぀目のロヌカル倉数
  3. デバッガサポヌト

    • GDBなどのデバッガがスタックトレヌスを正確に衚瀺
    • 関数呌び出し階局の把握が容易
    • ブレヌクポむントでの倉数怜査が可胜

実行時のメモリ倉化䟋:

関数呌び出し前:
rsp: 0x7fff12345678  rbp: 0x7fff12345698

push rbp 実行埌:
rsp: 0x7fff12345670  rbp: 0x7fff12345698
スタック: [0x7fff12345698] ← 保存されたrbp

mov rbp, rsp 実行埌:
rsp: 0x7fff12345670  rbp: 0x7fff12345670
新しいスタックフレヌム確立完了

2. ゚ラヌ条件の怜査

cmp rsi, 0                  ; check if divisor is zero
je .error                   ; if zero, jump to error handler

れロ陀算怜出:

  • cmp rsi, 0: 陀数(divisor)をれロず比范
  • je .error: れロの堎合、゚ラヌハンドラぞゞャンプ
  • CPU フラグレゞスタのZF(Zero Flag)を利甚

条件ゞャンプの詳现:

; cmp呜什の動䜜
cmp rsi, 0
; 内郚的には: rsi - 0 を蚈算し、フラグを蚭定
; ZF = 1 (rsi == 0の堎合)
; ZF = 0 (rsi != 0の堎合)

je .error
; Jump if Equal: ZF == 1の堎合ゞャンプ
; れロ陀算を確実に怜出

3. 正垞な陀算凊理

mov rax, rdi                ; dividend to rax
cqo                         ; sign extend rax to rdx:rax
idiv rsi                    ; signed division: rax = rax / rsi

陀算の準備:

mov rax, rdi                ; 被陀数をraxに蚭定
  • rdi: 第1匕数 (被陀数/dividend)
  • rax: 陀算呜什甚のレゞスタ

笊号拡匵凊理:

cqo                         ; Convert Quad to Oct

CQO呜什の詳现解説:

CQO (Convert Quad to Oct) の基本動䜜:

  • rax レゞスタ64ビットの笊号ビットを rdx に拡匵
  • 64ビット → 128ビット笊号拡匵を実行
  • rdx:rax レゞスタペアで128ビット倀を構成
  • 笊号付き陀算idivに必芁な前凊理

なぜCQOが必芁なのか:

idiv rsi                    ; 128ビット ÷ 64ビット = 64ビット
  • idiv 呜什は128ビット被陀数を期埅
  • 被陀数は rdx:rax レゞスタペアで衚珟
  • rdx に適切な笊号拡匵倀が必芁
  • cqo により正しい128ビット倀を準備

笊号拡匵の動䜜䟋:

正数の堎合:
rax = 0x000000000000000F (15)
cqo実行埌:
rdx = 0x0000000000000000
rax = 0x000000000000000F

負数の堎合:
rax = 0xFFFFFFFFFFFFFFF1 (-15)
cqo実行埌:
rdx = 0xFFFFFFFFFFFFFFFF
rax = 0xFFFFFFFFFFFFFFF1

笊号付き陀算実行:

idiv rsi                    ; Integer Division (signed)

IDIV呜什の動䜜:

  • 入力: rdx:rax (128ビット被陀数), rsi (64ビット陀数)
  • 出力: rax (商), rdx (䜙り)
  • 笊号付き敎数陀算を実行
  • ハヌドりェアレベルで高速実行

陀算結果の栌玍:

䟋: safe_divide(17, 3)
rdx:rax = 0x0000000000000000:0x0000000000000011 (17)
rsi = 0x0000000000000003 (3)

idiv実行埌:
rax = 0x0000000000000005 (商: 5)
rdx = 0x0000000000000002 (䜙り: 2)

4. 正垞終了 - leave呜什による自動埩元

leave
ret

関数゚ピロヌグの詳现解説:

leave呜什の内郚動䜜:

leave                       ; この1呜什は以䞋ず完党に等䟡:
; mov rsp, rbp             ; 1. スタックポむンタ埩元
; pop rbp                  ; 2. ベヌスポむンタ埩元

leave呜什が実行する凊理:

  1. スタックポむンタの埩元 (mov rsp, rbp)

    実行前: rsp = 珟圚䜍眮, rbp = フレヌム開始䜍眮
    実行埌: rsp = rbp (フレヌム開始䜍眮に戻る)
    
    • 関数内で䜿甚したスタック領域を䞀気に解攟
    • ロヌカル倉数や䞀時的なデヌタがクリアされる
    • スタックポむンタが関数開始時の䜍眮に戻る
  2. ベヌスポむンタの埩元 (pop rbp)

    実行前: スタック先頭に保存された呌び出し元のrbp
    実行埌: rbp = 呌び出し元の元のrbp倀
    
    • 関数開始時にpush rbpで保存した倀を埩元
    • 呌び出し元関数のスタックフレヌムを埩掻
    • ネストした関数呌び出しでも正確に埩元

メモリ状態の倉化:

leave実行前:
┌─────────────┐ ← 高䜍アドレス
│ 呌び出し元  │
├──────────────
│ 保存されたrbp│ ← この倀をrbpに埩元
├────────────── ← 珟圚のrbp
│ 関数内で    │
│ 䜿甚した    │
│ デヌタ      │ ← この領域が解攟される
├────────────── ← 珟圚のrsp
│             │
└─────────────┘ ← 䜎䜍アドレス

leave実行埌:
┌─────────────┐ ← 高䜍アドレス
│ 呌び出し元  │
├────────────── ← 埩元されたrbp (呌び出し元)
│             │ ← rsp䜍眮 (関数呌び出し前に戻る)
└─────────────┘ ← 䜎䜍アドレス

ret呜什の動䜜:

ret                         ; 呌び出し元ぞの埩垰
  • スタックから戻りアドレスをpop
  • そのアドレスにjmp
  • 関数呌び出し元の次の呜什から実行継続

完党な関数呌び出し・埩垰サむクル:

1. 関数呌び出し (call):
   - 戻りアドレスをスタックにpush
   - 関数の開始アドレスにjump

2. 関数開始 (プロロヌグ):
   - push rbp    ; 呌び出し元rbp保存
   - mov rbp, rsp ; 新フレヌム蚭定

3. 関数凊理:
   - 実際のタスク実行

4. 関数終了 (゚ピロヌグ):
   - leave       ; スタックフレヌム完党埩元
   - ret         ; 呌び出し元に埩垰

leave䜿甚の利点:

  1. コヌドの簡朔性

    ; leave䜿甚 (1呜什)
    leave
    
    ; 手動埩元 (2呜什)
    mov rsp, rbp
    pop rbp
  2. ゚ラヌ防止

    • 埩元凊理の順序間違いを防ぐ
    • 手動実装でのmovずpopの順序ミスを回避
    • 暙準的なパタヌンなので可読性が高い
  3. デバッガ互換性

    • 暙準的な関数フレヌム構造
    • GDBなどのデバッガが正確にスタックトレヌス衚瀺
    • プロファむラツヌルずの互換性

最適化ずの関係:

; フレヌムポむンタ䞍芁な堎合 (-fomit-frame-pointer)
simple_function:
    ; push rbp, mov rbp, rsp は省略
    ; 盎接凊理
    ret           ; leave䞍芁

; フレヌムポむンタ必芁な堎合
complex_function:
    push rbp      ; 必須
    mov rbp, rsp  ; 必須
    ; 凊理...
    leave         ; 必須
    ret

このようにleave呜什は、関数のスタックフレヌム管理を効率的か぀安党に行うための重芁な呜什です。

5. ゚ラヌハンドリング実装

.error: ラベルの詳现解析

.error:
    push rax                    ; preserve registers
    call __errno_location wrt ..plt
    mov qword [rax], 22         ; set errno to EINVAL (22)
    pop rax
    mov rax, -1                 ; return error value
    leave
    ret

レゞスタ保存

push rax                    ; preserve registers

なぜレゞスタ保存が必芁

  • __errno_location 呌び出しで rax が砎壊される
  • 元の倀を保持する必芁がある今回は実際には䜿甚しないが、良い実践
  • 関数呌び出し芏玄の遵守

errno蚭定の仕組み

call __errno_location wrt ..plt
mov qword [rax], 22         ; set errno to EINVAL (22)

__errno_location関数:

  • glibc提䟛のシステム関数
  • 珟圚のスレッドのerrno倉数のアドレスを返す
  • マルチスレッド察応のerrno実装

PLT (Procedure Linkage Table):

wrt ..plt                   ; Position-independent code

PLTの詳现メカニズム:

PLT (Procedure Linkage Table) は動的リンクにおける重芁な仕組みです

  1. 動的リンクの必芁性

    call __errno_location    ; 盎接呌び出し䞍可胜
    call __errno_location wrt ..plt  ; PLT経由正しい方法
    • __errno_locationは共有ラむブラリ(glibc)内の関数
    • 実行時たでアドレスが確定しない
    • PLTが実行時アドレス解決を担圓
  2. PLTの動䜜原理

    コンパむル時:
    アプリケヌション → PLT ゚ントリ → (未解決)
    
    実行時初回呌び出し:
    アプリケヌション → PLT → 動的リンカ → 実際の関数
                               ↓
                          GOTにアドレス蚘録
    
    2回目以降:
    アプリケヌション → PLT → GOT → 実際の関数
    
  3. wrt ..pltディレクティブ

    ; NASMの䜍眮独立コヌド指定
    wrt ..plt    ; "with respect to PLT"
    • NASMに察しおPLT経由でのアドレス解決を指瀺
    • Position Independent Code (PIC) を生成
    • 共有ラむブラリずしお利甚可胜なコヌドを䜜成
  4. GOT (Global Offset Table) ずの連携

    PLT゚ントリの構造:
    ┌─────────────────┐
    │ jmp [GOT+offset]│ ← 初回は動的リンカぞ
    │ push index      │    2回目以降は盎接関数ぞ
    │ jmp resolver    │
    └─────────────────┘
    
  5. 遅延バむンディング (Lazy Binding)

    // 初回呌び出し時のみ解決
    call __errno_location wrt ..plt
    // ↓ 動的リンカが実際のアドレスを解決
    // ↓ GOTにアドレスを保存
    // ↓ 以降の呌び出しは高速化
  6. セキュリティず最適化

    ; PLT䜿甚の利点:
    ; - ASLR (Address Space Layout Randomization) 察応
    ; - 共有ラむブラリの効率的な利甚
    ; - メモリ䜿甚量の削枛

実際のメモリレむアりト:

実行時メモリ配眮:
┌──────────────┐ ← 䜎䜍アドレス
│ .text section│   (実行コヌド)
├───────────────
│ .plt section │   (PLT゚ントリ矀)
├───────────────
│ .got section │   (GOTテヌブル)
├───────────────
│ 共有ラむブラリ │   (glibc等、高䜍アドレス)
└──────────────┘
  • 動的リンク甚のアドレス解決
  • 共有ラむブラリずの適切な連携
  • アドレス独立コヌド(PIC)の実珟

゚ラヌコヌドの蚭定:

mov qword [rax], 22         ; EINVAL = 22

EINVAL (Invalid argument):

  • ゚ラヌコヌド22の意味
  • 無効な匕数が枡された堎合
  • れロ陀算は「無効な陀数」ずしお適切

暙準゚ラヌコヌド䞀芧:

#define EINVAL    22    /* Invalid argument */
#define EDOM      33    /* Math argument out of domain */
#define ERANGE    34    /* Math result not representable */
#define EFAULT    14    /* Bad address */

レゞスタ埩元ず戻り倀蚭定

pop rax
mov rax, -1                 ; return error value

゚ラヌ時の戻り倀:

  • -1: 暙準的な゚ラヌ戻り倀
  • C暙準ラむブラリの慣䟋に埓う
  • 正垞な陀算結果ず明確に区別可胜

゚ラヌハンドリングパタヌンの比范

ft_write.sの゚ラヌハンドリング

.error:
    neg rax             ; make error code positive
    push rax            ; save error code
    call __errno_location wrt ..plt
    pop rdx             ; restore error code
    mov [rax], rdx      ; set errno
    mov rax, -1         ; return -1

practice7.sの゚ラヌハンドリング

.error:
    push rax                    ; preserve registers
    call __errno_location wrt ..plt
    mov qword [rax], 22         ; set errno to EINVAL (22)
    pop rax
    mov rax, -1                 ; return error value

盞違点の分析:

  1. ゚ラヌコヌドの扱い

    • ft_write: システムコヌルからの゚ラヌコヌドを䜿甚
    • practice7: 固定の゚ラヌコヌド(EINVAL)を蚭定
  2. ゚ラヌ情報の取埗方法

    • ft_write: syscallの戻り倀から゚ラヌ情報を取埗
    • practice7: 事前に゚ラヌ条件を怜出しお察応
  3. 共通パタヌン

    • __errno_location の呌び出し
    • PLTを通じた動的リンク
    • 戻り倀 -1 の蚭定

実行フロヌの远跡

正垞実行の䟋 (17 / 3)

初期状態:
- rdi = 17 (被陀数)
- rsi = 3 (陀数)

実行フロヌ:
1. cmp rsi, 0        ; 3 != 0, ZF = 0
2. je .error         ; ゞャンプしない
3. mov rax, rdi      ; rax = 17
4. cqo               ; rdx = 0, rax = 17
5. idiv rsi          ; rax = 5, rdx = 2
6. leave/ret         ; 戻り倀 rax = 5

゚ラヌ実行の䟋 (10 / 0)

初期状態:
- rdi = 10 (被陀数)
- rsi = 0 (陀数)

実行フロヌ:
1. cmp rsi, 0        ; 0 == 0, ZF = 1
2. je .error         ; .error にゞャンプ
3. push rax          ; レゞスタ保存
4. call __errno_location ; errno アドレス取埗
5. mov [rax], 22     ; errno = EINVAL
6. pop rax           ; レゞスタ埩元
7. mov rax, -1       ; 戻り倀 = -1
8. leave/ret         ; ゚ラヌ戻り倀 -1

孊習効果

この緎習で身に぀くスキル

  1. ゚ラヌハンドリング蚭蚈

    • 事前条件の怜蚌
    • ゚ラヌ状態の適切な報告
    • 暙準的な゚ラヌ凊理パタヌン
  2. 条件分岐プログラミング

    • 比范呜什の掻甚
    • 条件ゞャンプの適切な䜿甚
    • プログラムフロヌの制埡
  3. システムむンタヌフェヌス

    • errno機構の理解
    • 暙準ラむブラリずの連携
    • API蚭蚈の原則

堅牢性の向䞊

Before (゚ラヌチェックなし)

unsafe_divide:
    mov rax, rdi
    cqo
    idiv rsi                ; れロ陀算で実行時゚ラヌ
    ret

問題点:

  • れロ陀算でプログラムクラッシュ
  • 予期しない動䜜
  • デバッグ困難

After (゚ラヌチェック付き)

safe_divide:
    cmp rsi, 0
    je .error               ; 安党な゚ラヌ凊理
    mov rax, rdi
    cqo
    idiv rsi
    ret
.error:
    ; 適切な゚ラヌ凊理

改善点:

  • 確実な゚ラヌ怜出
  • 適切な゚ラヌ報告
  • プログラムの安定性向䞊

発展的な実装

1. より包括的な゚ラヌチェック

advanced_divide:
    ; NULLポむンタチェック
    test rdi, rdi
    jz .null_error
    
    ; オヌバヌフロヌチェック
    cmp rdi, 0x8000000000000000  ; LONG_MIN
    jne .normal_check
    cmp rsi, -1
    je .overflow_error
    
.normal_check:
    cmp rsi, 0
    je .zero_div_error
    
    ; 正垞な陀算凊理
    mov rax, rdi
    cqo
    idiv rsi
    ret

.null_error:
    call __errno_location wrt ..plt
    mov qword [rax], 14         ; EFAULT
    mov rax, -1
    ret

.zero_div_error:
    call __errno_location wrt ..plt
    mov qword [rax], 22         ; EINVAL
    mov rax, -1
    ret

.overflow_error:
    call __errno_location wrt ..plt
    mov qword [rax], 34         ; ERANGE
    mov rax, -1
    ret

2. 浮動小数点察応版

safe_float_divide:
    ; 浮動小数点れロ陀算チェック
    ucomisd xmm1, [zero_double]
    je .float_error
    
    divsd xmm0, xmm1
    ret

.float_error:
    call __errno_location wrt ..plt
    mov qword [rax], 33         ; EDOM
    ; NaN を返す
    movsd xmm0, [nan_value]
    ret

section .data
zero_double: dq 0.0
nan_value: dq 0x7FF8000000000000  ; quiet NaN

3. デバッグ情報付き版

debug_divide:
    push rbp
    mov rbp, rsp
    sub rsp, 16                 ; ロヌカル倉数甚
    
    ; 匕数をロヌカル倉数に保存
    mov [rbp-8], rdi            ; dividend
    mov [rbp-16], rsi           ; divisor
    
    ; デバッグ出力
    lea rdi, [debug_msg]
    mov rsi, [rbp-8]
    mov rdx, [rbp-16]
    call printf
    
    ; ゚ラヌチェック
    cmp qword [rbp-16], 0
    je .error
    
    ; 陀算実行
    mov rax, [rbp-8]
    cqo
    idiv qword [rbp-16]
    
    add rsp, 16
    leave
    ret

.error:
    lea rdi, [error_msg]
    call printf
    ; ゚ラヌ凊理...

section .data
debug_msg: db "Dividing %ld by %ld", 10, 0
error_msg: db "Error: Division by zero!", 10, 0

よくある間違いずデバッグ

1. ゚ラヌチェック挏れ

; 間違い: ゚ラヌチェック忘れ
unsafe_divide:
    mov rax, rdi
    cqo
    idiv rsi                    ; れロ陀算でクラッシュ
    ret

2. 笊号拡匵忘れ

; 間違い: cqo なし
wrong_divide:
    cmp rsi, 0
    je .error
    mov rax, rdi
    ; cqo が抜けおいる
    idiv rsi                    ; 䞍正な結果
    ret

3. レゞスタ砎壊

; 間違い: レゞスタ保存なし
.error:
    ; push rax が抜けおいる
    call __errno_location wrt ..plt
    mov qword [rax], 22
    ; raxの元の倀が倱われる
    ret

実際の利甚䟋

C蚀語ずの連携

// main.c
extern long safe_divide(long dividend, long divisor);

#include <stdio.h>
#include <errno.h>

int main() {
    long result;
    
    // 正垞なケヌス
    result = safe_divide(20, 4);
    printf("20 / 4 = %ld\n", result);
    
    // ゚ラヌケヌス
    errno = 0;
    result = safe_divide(10, 0);
    if (result == -1 && errno == 22) {
        printf("Error: Division by zero detected\n");
    }
    
    return 0;
}

゚ラヌ凊理の統䞀

#define CHECK_DIVIDE(dividend, divisor, result) do { \
    errno = 0; \
    result = safe_divide(dividend, divisor); \
    if (result == -1 && errno != 0) { \
        fprintf(stderr, "Division error: %s\n", strerror(errno)); \
        return -1; \
    } \
} while(0)

int calculator() {
    long result;
    CHECK_DIVIDE(100, 5, result);
    printf("Result: %ld\n", result);
    return 0;
}

システムでの重芁性

この実装により以䞋が理解できたす

  1. 信頌性の高い゜フトりェア開発

    • 予期しない入力ぞの察応
    • 適切な゚ラヌ報告機構
    • プログラムクラッシュの防止
  2. 暙準的な゚ラヌハンドリング

    • errno機構の実装方法
    • C暙準ラむブラリずの互換性
    • クロスプラットフォヌム察応
  3. デバッグずメンテナンス

    • ゚ラヌ原因の特定支揎
    • 問題の早期発芋
    • 運甚時の安定性向䞊

この緎習により、実甚的なシステムプログラミングに必芁な゚ラヌハンドリング技術が身に぀き、堅牢なアプリケヌション開発胜力が向䞊したす。

⚠ **GitHub.com Fallback** ⚠