2016-02-25 14 views
5

Voglio scrivere una funzione generica che fa una chiamata di sistema. Qualcosa comeQual è il tipo di argomenti per le chiamate di sistema su Linux?

long my_syscall2(long number, long arg1, long arg2); 

Voglio che sia il più portabile possibile. L'implementazione è ovviamente diversa per tutte le architetture. Anche la firma della funzione deve essere diversa? Posso usare long o dovrei usare qualcos'altro?

Ecco le possibili soluzioni che ho trovato:

  • Il kernel utilizza alcuni dark magic: (__SYSCALL_DEFINEx chiama __SC_LONG per ottenere il tipo, __SC_LONG contiene magia). Ho sentito da qualche parte che i tipi nello spazio utente non sono sempre gli stessi dello spazio del kernel, quindi non so se posso usarlo.
  • musl-libc uses long for all architectures that it supports except x32: (definito in [arch] /syscall_arch.h).
  • È possibile trovare la documentazione per tutte le architetture e i compilatori di processori che desidero supportare, cercare le dimensioni dei registri e le dimensioni dei tipi interi e selezionare qualsiasi tipo di intero con le stesse dimensioni dei registri.

Quindi credo che la domanda è: "C'è qualche regola che dice 'il tipo di argomenti chiamate di sistema sono sempre long con alcune eccezioni come x32' o ho bisogno di cercare la documentazione per ogni architettura e compilatore?"

Modifica: So che alcune chiamate di sistema richiedono puntatori e altri tipi come parametri. Voglio scrivere funzioni generiche che possono chiamare qualsiasi chiamata di sistema, con tipi di parametri generici. Questi tipi di parametri generici dovrebbero essere abbastanza grandi da contenere uno qualsiasi dei tipi di parametri effettivi. So che è possibile perché esiste la funzione syscall().

Edit2: Ecco un'altra soluzione parziale a questo problema.

Le implementazioni di queste funzioni attualmente simile a questa:

static __inline long my_syscall2(long number, long arg1, long arg2) 
{ 
    unsigned long ret; 
    __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(number), "D"(arg1), "S"(arg2) 
         : "rcx", "r11", "memory"); 
    return ret; 
} 

La parte interessante è "=a"(ret), ciò significa che il valore di ritorno chiamata di sistema che viene memorizzato nel registro a deve essere salvato nella variabile ret. Invece di scrivere una funzione che crea una variabile locale, crea syscall, salva il suo valore di ritorno nella variabile e restituisce la variabile Potrei scrivere una macro che crea syscall e memorizza il risultato in una variabile fornita dal chiamante. Si sarebbe simile a questa:

#define my_syscall2(RET, NUMBER, ARG1, ARG2) \ 
    __asm__ __volatile__ ("syscall" : "=a"(RET) : "a"(NUMBER), "D"(ARG1), "S"(ARG2) \ 
         : "rcx", "r11", "memory"); 

e sarebbe utilizzato in questo modo:

long result; 
void * arg1; 
int arg2; 
my_syscall2(result, <syscall number>, arg1, arg2); 

In questo modo non ho bisogno di sapere registrati dimensione e tipo integer che è abbastanza grande per contenere un valore del registro.

+0

Come gestireste le chiamate di sistema che non richiedono alcun parametro? – edmz

+0

@black Definirei 6 funzioni my_syscall {0-5} – alkedr

+1

In realtà è più complesso di quanto si possa rispondere qui succintamente (a meno che non si specifichi un'architettura), ma sentitevi liberi di utilizzare le mie implementazioni di dominio pubblico di syscall() e syscall0 () -syscall6() funziona su https://github.com/technosaurus/BQC ... il cast è troppo lungo per i parametri – technosaurus

risposta

1

Non c'è una soluzione generale. Se si vuole rendere il codice ultra-multiarchitettura si può semplicemente fare qualcosa di simile:

#if ARCH_WITH_32BIT_REGS 
typedef uint32_t reg_size_int_t; 
#elif ARCH_WITH_64BIT_REGS 
typedef uint64_t reg_size_int_t; 
#elif ARCH_WITH_16BIT_REGS 
typedef uint16_t reg_size_int_t; 
.... 
#endif 

reg_size_int_t syscall_1(reg_size_t nr, reg_size_t arg0); 
... 

Ma per le architetture più comuni utilizzati dimensione del registro è uguale a lungo.

3

Suggerisco di utilizzare la chiamata di sistema esistente syscall, anziché provare a scriverne una per conto proprio. Sembra fare esattamente quello che vuoi. Consulta la sezione "Requisiti specifici dell'architettura" della pagina di manuale per una discussione delle domande valide che hai sollevato.

+2

Stranamente, 'syscall()' non è un syscall, ma un wrapper di libreria attorno alle istruzioni syscall rilevanti. – EOF

+1

Sì, richiede anche una libreria c standard con estensioni gnu e memorizza il codice di errore nella variabile errno globale invece di restituirlo. Devo ammettere che non si tratta di problemi importanti, ma mi trovavo a vagare se c'è un modo semplice per non usare tutto questo. – alkedr

+1

Puoi anche modificare il codice sorgente di 'syscall' in base alle tue esigenze specifiche. –

4

Gli argomenti di chiamata di sistema vengono inoltrati nei registri. La dimensione è quindi limitata alla dimensione di un registro della CPU.Cioè, 32 bit su un'architettura a 32 bit, 64 bit su un'architettura a 64 bit. I numeri in virgola mobile non possono essere passati al kernel in questo modo. Tradizionalmente il kernel non usa istruzioni in virgola mobile (e potrebbe non essere in grado di come lo stato FPU di solito non è salvato in entrata nel kernel), quindi cerca di evitare i numeri in virgola mobile nelle tue chiamate di sistema.

Chiamate di sistema che utilizzano argomenti di tipo minore zero o firmano estensioni. Le chiamate di sistema che utilizzano tipi di argomenti più grandi possono dividere l'argomento in più registri.

Le chiamate di sistema (come mmap()) che hanno molti parametri possono essere implementate passando i parametri come puntatore a una struttura, ma questo ha un sovraccarico misurabile delle prestazioni, quindi evitare di progettare chiamate di sistema con più di cinque parametri.

Alla fine della giornata, utilizzare i tipi appropriati per il valore che si desidera inviare. Lascia che la libc si occupi di mettere i dati nei posti giusti.

Problemi correlati