Estadísticas
Miembros:
364.359
Online:
1.157
Hilos:
1.472.706
Mensajes:
27.962.631
Stats

Índice de foros Wii Scene

[INFO] mload: la llave del starlet (cIOS 202/222/223)

Foro dedicado al software casero y los diferentes hacks no relacionados con backups

Moderadores: jamonazo2000, Petiso Carambanal

Hermes
MegaAdicto!!!
 
Mensajes: 11053
Registrado: 18 Ene 2003

[INFO] mload: la llave del starlet (cIOS 202/222/223)

Mensajepor Hermes 14 Sep 2009 10:41

Artículo pasado a la wiki. se deja el contenido original del mensaje a fin de que el creador pueda revisarlo y posteriormente eliminar esta parte del mensaje, para dejar solo el contenido wiki.

Hola.

Supongo que mas o menos, tenéis una idea vaga de lo que es un custom IOS. Un custom IOS (o cIOS para abreviar) no es mas que una modificación o añadido que se le hace a los IOS originales para conseguir nuevas prestaciones y por ejemplo, poder emular la unidad de DVD desde un disco duro para cargar un backup, pero también puede servir para abrir una puerta nueva al Homebrew y con esa idea diseñé mload.

mload es un módulo (un programa) como cualquier otro de los que componen el IOS, orientado como una herramienta que suministrara una forma de cargar otros módulos externos desde el PPC, hacia el Starlet y así permitir que las aplicaciones pudieran proporcionar de forma personalizada, sus propios módulos, sin necesidad de tener que instalar un nuevo cIOS cada vez que se lleve a cabo una modificación de cualquier tipo. De esta forma, se facilita el desarrollo y se evitan colisiones e incompatibilidades indeseadas por que el cIOS está preparado para trabajar de forma diferente.

Trabajar en el Starlet no es sencillo, puesto que por un lado, hay que convivir con el resto de módulos que componen el IOS, respetando sus direcciones de memoria, etc, pero también hay una serie de protecciones de memoria y permisos que hacen que por ejemplo, si desde un módulo trato de escribir en determinadas áreas, se produzca un cuelgue (por una excepción). Además, nosotros no sabemos casi nada y tenemos que avanzar a tientas probando cosas.

Así pues, cuando desarrollé mload lo primero que tuve que buscar fue una zona de memoria que estuviera libre y que proporcionara un espacio lo suficientemente amplio para que pudiéramos cargar nuestros módulos: la zona elegida fue la comprendida entre 0x13700000 y 0x1377ffff. Es decir, 512KB para nuestros propósitos, que es un espacio aceptable y que parece que no da problemas. También tuve que investigar el uso de las syscalls y aprender a ejecutar hilos y controlarlos, así como la utilización de los timers, etc (cosa de la que también hablaré mas adelante)

Características generales de mload

mload se compone de tres secciones de memoria. El código ejecutable se carga en la dirección 0x138c0000, el espacio de variables, la pila etc, a partir 0x138c8000 y el área que nos interesa, en 0x13700000, ajustando los permisos como lectura y escritura, simplemente (lo podéis ver en la carpeta de scripts en el fichero link.ld de los fuentes del módulo).

Cuando compilamos el programa, obtenemos un módulo con unas dimensiones un tanto respetables (unos 643 KB!), pero éste módulo elf no está preparado para que el IOS lo pueda asimilar tal cual y debe pasar por una herramienta llamada stripios (de nuevo os remito a los fuentes) que yo modifiqué para que pudiera comprimir una de las secciones que le indiquemos, a un tamaño de 1 solo byte (por tratarse de un espacio vacío en realidad) utilizando uno de los trucos del formato elf .

De esa forma, la sección 0x13700000 reduce todo su tamaño (en el makefile podéis ver la línea "@$(STRIPIOS) $< $@ strip 0x13700000" que se ocupa de ello) y el módulo a incluir en el IOS es de solo 7KB como resultado (esto si que es "majia" de la buena XD ).

Desde el PPC (también desde el Starlet obviamente), podemos acceder a una serie de funciones para manipular la memoria o ejecutar módulos desde el dispositivo "dev/mload" (que luego trataremos con más detalle), principalmente, en el área de trabajo entre 0x13700000 y 0x1377ffff, claro está.

En la última versión de mload se suministra también una serie de funciones nuevas, cuyo propósito principal es ganar permisos a la
hora de realizar ciertas tareas, mediante Interrupciones por Software (SWI) desde el Starlet y de esa forma poder realizar casi cualquier cosa (por ejemplo, yo utilizo estas herramientas para cambiar el vector original de interrupciones y procesarlo desde el módulo EHCI o copiar zonas de memoria críticas, con las interrupciones deshabilitadas y todos los permisos).

Mload responde al servicio "svc 0xcc", permitiendo registrar otros servicios desde 0x00 a 0xff (exceptuando 0xcc obviamente) y tratando de forma interna el servicio "svc 0xab", en concreto la función os_puts(""); para obtener logs reportados por otros módulos (obviamente, se puede registrar otra función para tratar 0xab y redirigir los mensajes a otro dispositivo)

Otro detalle interesante, es que mload es cargado usando la máxima prioridad admitida (0x79) con el fin de permitir que podamos crear hilos con el abanico más amplio de posibilidades (un hilo no puede crear otro hilo que tenga mayor prioridad que el).

Mload identifica el IOS base (entre 36, 37,38 y 60) y hace los parches necesarios de forma directa e interna, aunque por el momento, solo ha podido cargarse con éxito con base 36 y 38. También se ocupa de pre-inicializar el driver EHCI.

Preparación del cIOS: parches y módulos

El instalador actual, realiza dos series de parches: uno destinado al modulo que yo suelo denominar "ES" de forma genérica para identificarlo (aunque en realidad contiene más cosas, como un cargador de elf con distintas secciones con el kernel, etc). Y otro que va destinado al modulo "DIP". En cIOS 202 se capan los parches destinados a facilitar la conexión de dip_plugin (código en formato binario que es cargado en la dirección 0x1377E000 que hace de interfaz entre el módulo DIP y el módulo EHCI para la emulación de DVD)

Los parches de la parte ES se han transferido al módulo mload y se han reemplazado en el instalador por dos nuevos parches que actúan sobre la tabla de saltos de la syscall os_software_IRQ (función que se usa para habilitar una interrupción ligada a un dispositivo), en concreto para os_software_IRQ(4) (para poder habilitar las interrupciones EHCI, saltando una comprobación) y para os_software_IRQ(9) (interrupción fuera de uso en el Starlet y que aquí se utiliza para saltar al crt0.s de mload y ganar permisos de sistema, con los que poder llevar a cabo los parches necesarios desde mload). A efectos prácticos, una sola llamada a os_software_IRQ(9) durante la ejecución de mload, es lo que hace que mload se vuelva especial y tengamos un gran poder en nuestras manos.

El módulo EHCI, lo compilo para ser cargado en la dirección 0x13700000 hasta la 0x1373ffff (ocupa la mitad del espacio disponible!) pero también hay que decir que en realidad se compone de tres módulos en uno (EHCI+USB Storage+WBFS) y soy generoso proporcionándole espacio para la pila y asignación de memoria .

Tanto el módulo EHCI (ehcmodule.elf) como dip_plugin, no son cargados por el cIOS, si no que son módulos externos que se alojan en memoria por las aplicaciones (por ejemplo, uLoader) y cada cual puede repartir a su gusto la memoria como le parezca (cargando otro tipo de módulos o modificando sus direcciones de linkado)

Caja de Herramientas de dev/mload

Para trabajar desde el PPC con mload, en examples/ppc/libmload podeis encontrar un conjunto de herramientas con las que realizar diferentes tareas.

En mload.h podéis ver el catálogo de las funciones disponibles. Todas las funciones, llaman a mload_init() de forma interna y retornan un valor negativo en caso de error. El único cuidado especial que debemos de tener, es de llamar a mload_close(); antes de cargar un nuevo IOS si vamos a seguir utilizando los servicios de dev/mload.

Aquí se listan las principales funciones:

IOS

int mload_get_IOS_base();

ret: devuelve el IOS utilizado como base del cIOS (FFS, ES, IOSP), actualmente el 38. Esto es importante conocerlo porque por ejemplo, si vamos a cambiar el vector de interrupciones, realizar parches de algún tipo o incluso llamar funciones del sistema desde fuera, las localizaciones son diferentes en los distintos IOS


Carga de módulos

int mload_module(void *addr, int len);

Función pensada para cargar módulos pequeños desde la memoria del PPC de forma directa. Recomiendo utilizar mejor mload_elf()

addr: dirección del módulo .elf en memoria, alineada a 32
len: longitud del módulo elf

ret: devuelve negativo si hubo error o la id del hilo creado en el Starlet.



int mload_elf(void *my_elf, data_elf *data_elf);

Función utilizada para copiar en la memoria del Starlet las diferentes secciones de un módulo en formato elf.

my_elf: dirección del módulo .elf en memoria, alineada a 32
data_elf: estructura que es inicializada por mload elf situando los valores de inicio, prioridad, stack, etc

ret: 0->OK <0 -> Error



int mload_run_thread(void *starlet_addr, void *starlet_top_stack, int stack_size, int priority);

Función utilizada para ejecutar un hilo en el starlet (lo normal es utilizarla en conjunción con los datos devueltos por mload_elf() en data_elf)

starlet_addr: dirección de inicio del hilo del programa

starlet_top_stack: dirección superior de la pila (la pila es descendente).

stack_size: tamaño total de la pila.

priority: prioridad de 0x1 a 0x79 (maxima)

ret: devuelve negativo si hubo error o la id del hilo creado en el Starlet.



Leer/Escribir memoria

int mload_seek(int offset, int mode);

Posicionar el puntero en memoria para las operaciones de lectura/escritura

offset: dirección de memoria absoluta/relativa en el Starlet

mode: SEEK_SET normalmente.

ret: <0 error



int mload_read(void* buf, u32 size);
int mload_write(const void * buf, u32 size);


lectura/escritura desde la posición del puntero en el Starlet

buf: buffer donde se leerá o desde el que se escribirá. Se recomienda alinear a 32

size: bytes a leer o escribir

ret: <0 error. Numero de bytes leidos/escritos.



int mload_memset(void *starlet_addr, int set, int len);

función especial que equivale a memset() para el Starlet


Especiales

void * mload_get_ehci_data();

Devuelve la dirección de la estructura EHCI. Se utiliza en ehcmodule.elf.


int mload_get_log();

Función que situa el puntero de lectura/escritura en la dirección de inicio del área reservada para logs mediante la función os_puts(). Normalmente, tiene una extensión de 4KB y se compone de una cadena de caracteres terminada en '\0'.

Debería ir seguida de un mload_read() y tiene su utilidad para volcar su contenido a la SD, por ejemplo.

ret: si falla retorna <0. Devuelve el tamaño máximo del buffer de log.



int mload_getw(const void * addr, u32 *dat);
int mload_geth(const void * addr, u16 *dat);
int mload_getb(const void * addr, u8 *dat);

int mload_setw(const void * addr, u32 dat);
int mload_seth(const void * addr, u16 dat);
int mload_setb(const void * addr, u8 dat);


Funciones especiales para leer/escribir registros o zonas de memoria sin cachear accesibles en modo usuario desde el Starlet.


Syscalls

Bien, ya conocemos las funciones de mload que nos permiten leer/escribir/cargar y ejecutar en memoria, código o modulos desde el PPC, pero de poco nos sirve eso si no conocemos una serie de funciones que nos proporciona el sistema para construir dichos módulos y poder comunicarnos con ellos. Así que creo que vendría bien darle un repaso a las principales syscalls que podemos necesitar para trabajar con nuestros módulos.

Antes de nada, me gustaría señalar que no podemos utilizar ciertas funciones de la librería estándar como malloc(), free() o printf().

Memoria

int os_heap_create(void* ptr, int size);

Función que se utiliza para crear un montículo de memoria que utilizaremos en nuestro módulo de forma privada. Dicho de otra forma, es como si indicáramos en un programa una zona de memoria que luego se asignará con malloc() (que ya he mencionado que no se debe utilizar)

ptr: inicio del area de memoria utilizada como montículo (alineado a 32, probablemente)

size: tamaño en bytes del montículo

ret= <0 error. Id del Heap



void* os_heap_alloc(int heap, unsigned int size);

El equivalente al malloc().

heap: id del heap a utilizar. 0 parece ser el montículo global

size: tamaño en bytes asignar.

ret= dirección de la memoria asignada o NULL



void os_heap_free(int heap, void* ptr);

El equivalente a free().

heap -> id del heap.

ptr ->dirección a liberar


Caché

Como sabéis, los procesadores implementan memoria caché para acelerar el acceso de memoria, dado que la memoria RAM es más lenta, tanto para el código en ejecución (icache) como para datos (dcache). Las syscalls siguientes se utilizan para trabajar con la caché de datos.

void os_sync_before_read(void* ptr, int size);

Función a utilizar antes de las lecturas del procesador cuando se presume que han habido operaciones DMA (fuera de la caché) en el bloque de memoria especificado. Tengo una duda, sobre ésta función, que no he comprobado : no se si simplemente, invalida la caché o si además procede a refrescarla (si es el primer caso, las líneas de caché se irían refrescando a medida que fuéramos leyendo desde la RAM, por lo que si invalidamos la caché y al cabo de un segundo, por ejemplo, cambiáramos un dato vía DMA y después procediéramos a leer dicho dato, tendríamos el dato mas actual, porque la caché se refrescaría en el momento de leer, mientras que en el segundo caso, tendríamos el dato antiguo, puesto que la caché se habría refrescado cuando llamamos a os_sync_before_read() y la lectura se haría dentro de la cache (salvo que por alguna razón externa, el procesador hubiera descartado esos datos) y no serían visibles los cambios fuera de caché, salvo que llamáramos de nuevo a os_sync_before_read(). Solo se que el ARM tiene una instrucción específica para invalidar caché, pero aquí hay otras operaciones un tanto raras que sugieren que puede haber una invalidación y refresco de la caché.

ptr: dirección de memoria. Ojo con esto porque las lineas de caché trabajan con 32 bytes, por lo que si se solapan datos (es decir, que el puntero no esté alineado a 32), se puede producir resultados imprevistos.

size: tamaño en bytes. Ojo con esto por que el tamaño es redondeado para ser divisible entre 32 y si se solapan datos (es decir, que el redondeo exceda del tamaño) se pueden producir resultados imprevistos



void os_sync_after_write(void* ptr, int size);

Función a utilizar después de las escrituras cuando se presume que se va a utilizar el bloque de memoria especificado en operaciones DMA (fuera de la caché), para que la caché se escriba en la memoria RAM.

ptr: dirección de memoria. Ojo con esto porque las lineas de caché trabajan con 32 bytes, por lo que si se solapan datos (es decir, que el puntero no esté alineado a 32), se puede producir resultados imprevistos.

size: tamaño en bytes. Ojo con esto por que el tamaño es redondeado para ser divisible entre 32 y si se solapan datos (es decir, que el redondeo exceda del tamaño) se pueden producir resultados imprevistos



Colas

Las colas (queue) son el mecanismo que nos permite sincronizar la ejecución de los distintos hilos, de forma que podemos registrar una cola para un dispositivo por ejemplo y ésta queda a la espera de que se le envíe un mensaje, que puede ser la dirección de una estructura de datos o simplemente una señal de otro tipo.

int os_message_queue_create(void* ptr, unsigned int id);

Función para crear una cola.

ptr: puntero donde se recibirán los mensajes de la cola.

id: Numero de entradas que soportará la cola. Cada entrada ocupa 4 bytes en la memoria señalada por ptr. Hay que tener mucho cuidado de no envioar mas mensajes de los que la cola pueda soportar (especialmente peligroso cuando trabajemos con timers). Por cierto, la etiqueta 'id' debería ser un 'max_entries' o algo similar.

ret: <0 si error. queuehandle asociado a la cola.



int os_message_queue_receive(int queue, unsigned int* message, unsigned int flags);

Función que aguarda a que una cola reciba un mensaje, interrumpiendo la ejecución del hilo.

queue: queuehandle (devuelto por os_message_queue_create())

message: Si es un dispositivo, lo normal es que sea la dirección de una estructura ipcmessage (ver syscalls.h)

flags: siempre a 0

ret: <0 error. Es posible que devuelva el numero de elementos en cola, pero no lo he comprobado.



int os_message_queue_send(int queue, unsigned int message, int flags);
int os_message_queue_send_now(int queue, unsigned int message, int flags);


Función para enviar un mensaje a una cola. Probablemente el send_now provoque la activación del hilo saltándose la prioridad de las colas.

queue: queuehandle (devuelto por os_message_queue_create())

message: dirección o mensaje a enviar a la cola.

flags: a 0

ret: <0 error



void os_message_queue_ack(void* message, int result);

Función que devuelve una respuesta del dispositivo mediante IPC. Realmente, no veo que relación tiene esto con la colas por que
es más bien una función de respuesta de dispositivo, que otra cosa ...

message: Dirección de la estructura ipcmessage que se recibió mediante os_message_queue_receive()

result: Resultado de la operación IPC solicitada (IOS_OPEN, IOS_CLOSE, ...)



Dispositivos

int os_device_register(const char* devicename, int queuehandle);

Función que asocia un nombre de dispositivo a una cola, para operaciones IPC.

devicename: Nombre del dispositivo: Ej "/dev/mload"

queuehandle: queuehandle (devuelto por os_message_queue_create())



int os_open(char* device, int mode);

Equivalente a IOS_Open en PPC.

device: nombre del dispositivo a abrir.

mode: flags de modo (O_CREAT, O_TRUNC, O_RDONLY, O_WRONLY, O_RDWR ... en include/sys/fcntl.h)

ret: <0 error, fd del dispositivo.



int os_close(int fd);

Cierra el dispositivo.

fd: fd del dispositivo

ret: <0 error



int os_seek(int fd, int offset, int mode);

Posiciona el puntero del dispositivo.

fd: fd del dispositivo

offset: posición dentro del dispositivo

mode: SEEK_SET, SEEK_END, SEEK_CUR...

ret: <0 error. Posición actual absoluta dentro del dispositivo



int os_read(int fd, void *d, int len);
int os_write(int fd, void *s, int len);


Funciones para lectura/escritura en dispositivo

fd: fd del dispositivo

s: d: direcciones fuente/destino para escritura/lectura

len: longitud en bytes.

ret: <0 Error. Numero de bytes escritos/leídos.



int os_ioctlv(int fd, int request, int bytes_in, int bytes_out, ioctlv *vector);

Función de control I/O mediante vectores. Conviene utilizar las funciones de caché apropiadas en las transferencias de datos.

fd: fd del dispositivo

request: indice función ioctlv

bytes_in: Numero de elementos de entrada (la etiqueta está mal, puesto que no son bytes)

bytes_out: Numero de elementos de salida (la etiqueta está mal, puesto que no son bytes)

vector: Dirección del array de vectores (tipo ioctlv en syscalls.h). in+out

ret: <0 error. Resultado depende de la función seleccionada.



int os_ioctl(int fd, int request, void *in, int bytes_in, void *out, int bytes_out);

Función de control I/O. Conviene utilizar las funciones de caché apropiadas en las transferencias de datos.

fd: fd del dispositivo

request: indice función ioctl

in: dirección buffer de entrada

bytes_in: longitud buffer de entrada

out: dirección buffer de salida

bytes_out: longitud buffer de salida

ret: <0 error. Resultado depende de la función seleccionada.



Timers

Los timers se utilizan para enviar un mensaje a la cola cada cierto tiempo. Internamente, son controlados por una alarma que genera una interrupción cada cierto tiempo, por lo que el si el tiempo del timer es muy bajo, seguramente tenga cierta distorsión (aparte de que la activación de las colas dependerá de la prioridad de los hilos, etc). La resolución de los timers se mide en microsegundos. Hay que tener especial cuidado de que el tiempo de repetición no sea demasiado corto y acabe por rebasar la cola asociada.

int os_create_timer(int time_us, int repeat_time_us, int message_queue, int message);

Función para crear un timer.

time_us: tiempo en microsegundos de la primera cuenta

repeat_time: tiempo en microsegundos de las siguientes cuentas. Si solo se requiere una cuenta, se puede fijar un tiempo muy alto aquí y proceder a parar el timer cuando se reciba el mensaje

message_queue: queuehandle de la cola que recibirá el mensaje

message: mensaje que recibirá la cola al cumplirse el tiempo (podría ser la dirección de una estructura, por ejemplo)

ret: <0 error al crear el timer. id del timer handle asociado



int os_stop_timer(int timer_id);

Para la cuenta de un timer. Se puede restablecer con os_restart_timer().

timer_id: Timer handle

ret: <0 error


int os_destroy_timer(int time_id);

Elimina un timer. Es posible que sea necesario pararlo primero con os_stop_timer().

timer_id: Timer handle

ret: <0 error



int os_restart_timer(int timer_id, int time_us);

Reinicia un timer que fue parado con os_stop_timer(). La cuenta es repetitiva, por lo que hay que tener cuidado que el tiempo no sea demasiado corto como para que se produzca un desborde en la cola asociada al timer.

timer_id: Timer handle

time_us: tiempo en microsegundos de las sucesivas repeticiones

ret: <0 error



int os_timer_now(int time_id);

Probablemente fuerce a un timer a enviar el mensaje y reiniciar su cuenta al ser llamada

timer_id: Timer handle

ret: <0 error



Interrupciones

int os_register_event_handler(int device, int queue, int message);

Registra una cola a un dispositivo que recibirá eventos mediante interrupciones. Solo es posible registrar si no hay otro evento registrado.

device: IRQ del dispositivo en cuestión 4->EHCI, 5->OHC0, etc (ver libcios/include/starlet.h)

queue: quehandle de la cola asociada

message: mensaje que se envía a la cola

ret: <0 si error



int os_unregister_event_handler(int device);

Elimina los eventos por interrupción para el dispositivo especificado.

device: IRQ del dispositivo en cuestión 4->EHCI, 5->OHC0, etc (ver libcios/include/starlet.h)

ret: <0 si error



int os_software_IRQ(int dev);

Función que habilita el uso de interrupciones en el dispositivo. Tiene una doble función: por un lado, elimina una posible interrupción pendiente para dicho dispositivo y por otro, ajusta la máscara de interrupciones (Hollywood) para que estas puedan producirse. Sin embargo, ésta syscall solo admite fijar desde IRQ4 (EHCI) en adelante y existe una comprobación (probablemente ligada a la ID del proceso) que hace que no en todos los casos se pueda fijarla interupción desde aquí (de ahí que uno de los parches de mload se dedique a habilitar os_software_IRQ(4).

Sin embargo, es posible acceder a los registros HW_ARMIRQFLAG y HW_ARMIRQMASK de forma directa mediante los servicios SWI de mload (como veremos mas adelante)

dev: IRQ del dispositivo en cuestión a partir de 4->EHCI, 5->OHC0, etc (ver libcios/include/starlet.h)

ret: <0 error




Hilos

NOTA: el thread ID=0 indica hilo actual en algunas funciones.

int os_thread_create( unsigned int (*entry)(void* arg), void* arg, void* stack, unsigned int stacksize, unsigned int priority, int autostart);

Función que crea un hilo de programa en el starlet. El hilo se encuentra parado de inicio y requiere el uso de os_thread_continue() para ejecutarse.

entry: función de inicio que recibirá el foco del hilo.

arg: argumento que se le pasará a la función de inicio

stack: dirección de memoria como tope de pila. Por ejemplo, si dedicamos u32 my_stack[STACK_SIZE] como pila del hilo, aquí habria que pasar &my_stack[STACK_SIZE] como dirección

stacksize: Tamaño en bytes de la pila

priority: prioridad entre 0x1 y 0x79. La prioridad no puede ser mayor que la del hilo donde se invoca ésta función (o no funcionará)

autostart: a 0. No parece funcionar como autostart y puede que no tenga uso o que sirva para indicar otra cosa.

ret: <0 Error. ID del hilo.



int os_thread_continue(int id);

Continua con la ejecución del hilo (después de crearse o de una parada)

id: ID del hilo

ret: <0 error



int os_thread_stop(int id);

Para la ejecución de un hilo.

id: ID del hilo

ret: <0 error



int os_get_thread_id(void);

Devuelve la ID del hilo actual.

ret: ID del hilo.



void os_thread_set_priority(int id, unsigned int priority);

Fija la prioridad actual del hilo.

id: ID del hilo

priority: prioridad entre 0x1 y 0x79. La función fallará si la prioridad es mayor que la de la creación del hilo.



int os_thread_get_priority(void);

Devuelve la prioridad del hilo actual.

ret: <0 error. Prioridad actual


Log

void os_puts(char *str);

Función que envía una cadena de texto al buffer de log



Códigos Fuentes

Antes de mirar algunos ejemplo de código, me gustaría explicar un poco la organización de los fuentes de mload, para que sepais donde buscar información:

apps
|--- cios_installer -> Instalador de los cIOS 202/222/223

cios_mload
|
|------- cios_installer -> Fuentes del instalador de cIOS
|
|------- ehcmodule -> Fuentes del módulo EHCI
|
|------- examples -> Ejemplos para PPC y Starlet. Librería mload para PPC
| |
| |---- ppc
| | |------ example1 -> Ejemplo simple que muestra la actividad de un módulo
| | |------ example2 -> Ejemplo que muestra la utilización del módulo FAT/FAT32
| | |------ libmload -> Librería utilizada en el apartado "Caja de Herramientas de mload"
| |
| |---- starlet
| | |------ example1 -> módulo que muestra el uso de timers, colas y multihilo (para resetear una cuenta)
| | |------ libfat -> módulo que soporta FAT/FAT32 con memorias SD
| |
| |---- stripios -> réplica de la utilidad stripios
|
|------- libcios -> Librerías base
| |
| |------ include
| | |------- starlet.h -> Información sobre registros (Hollywood) IRQ, GPIO, TIMER (sacado de wiibrew.org)
| | |------- swi_mload.h -> Librería de servicios SWI de mload (Avanzado)
| | |------- syscalls.h -> Librería de syscalls
| | |------- types.h -> tipos de datos usados en el Starlet
| |
| |------ source
| |------- swi_mload.c -> Librería de servicios SWI de mload (Avanzado)
| |------- swi_mload.s -> Código ensamblador para llamar SWI
| |------- syscalls.s -> Código ensamblador para llamar a las syscalls
|
|------- mload -> Fuentes del módulo mload
|
|------- stripios -> Utilidad que convierte los elfs normales a los que precisa el IOS
|
|------- tinyehci -> Fuentes del driver EHCI /USB Storage
|
|------- wii_wbfs -> Fuentes para trabajar con particiones WBFS



Organización de un módulo

Los módulos en el Starlet, se compilan en una dirección fija y además es necesario convertir el elf resultante mediante la utilidad stripios. Voy a explicar aquí como están estructurados los fuentes partiendo del módulo example1:

example1
|
|------- bin -> Aquí se alojará el módulo resultante
|------- scripts -> Aquí se encuentra nostart.specs y link.ld. Este último contiene el mapa de direcciones del módulo
|------- source
| |---- crt0.S -> Aquí se fija la prioridad inicial del módulo, la pila, etc. Y otras rutinas en ensamblador.
| |---- main.c -> el cuerpo del módulo propiamente dicho
|
|------- Makefile


Creando un hilo y un timer que lo active

En primer lugar, debemos declarar un espacio que utilizaremos para la pila:

#define THREAD_STACK 1024
u8 thread_stack[THREAD_STACK] __attribute__ ((aligned (32)));


Luego, debemos declarar una función que recibirá el foco del hilo:

int thread_start(void *arg)
{

/* creamos una cola que usaremos en el hilo para aguardar un evento que lo active
observar que en este caso, estamos utilizando como heaphandle 0, para asignar 32 bytes y una cola de 8 elementos (de 4 bytes)
*/

int thread_queuehandle = os_message_queue_create( os_heap_alloc(0, 0x20), 8);


/* vamos a definir un evento (en este caso un timer) que enviará un mensaje que activará el hilo cada 10 segundos */

os_create_timer(1000*1000*10, 1000*1000*10, thread_queuehandle, 0x555);

while(1)
{
u32 message;

/* aguarda a que la cola reciba un mensaje */
os_message_queue_receive(thread_queuehandle, (void*)&message, 0);

if(message==0x555)
{
// mensaje del timer recibido: hacer aquí lo que corresponda
}

}


return 0;
}



Ahora tocaría crear el hilo desde el main():

int my_thread_id=os_thread_create( (void *) thread_start, NULL, &thread_stack[THREAD_STACK], THREAD_STACK, 0x48, 0);

if(my_thread_id>=0) os_thread_continue(my_thread_id);


Y con este ejemplo, doy por finalizada la información básica. Tenéis suficiente código tanto en mload, ehcmodule como en los ejemplos, para ver otros usos y no tiene sentido que me extienda más sobre éste tema. En el siguiente post que tengo reservado, casi a continuación, trataré de explicar las funciones SWI de mload y algunos usos de forma mas avanzada.

Descarga de fuentes de mload

http://mods.elotrolado.net/~hermes/wii/ ... ll_3.5.rar
Ultima edición por comepiedras el 22 Dic 2011 16:33, editado 20 veces

JaRaBcN
Avatar de usuario
Va a ser legendario
 
Staff
 
Mensajes: 5474
Registrado: 25 Dic 2006
Ubicación: 11000010011 Xtag: JaRaBcN

Mensajepor JaRaBcN 14 Sep 2009 11:15

Impresionante explicación sobre como funciona.

La verdad se agradece saber que es lo que quieres que llegue hacer, y si realmente funciona y/o llega a funcionar como dices, puede abrir muchas opciones futuras para el que esté interesado en programar para Wii...

Gracias Hermes por tu trabajo desinteresado hacia la Scene de la Wii [ok]

Hermes
MegaAdicto!!!
 
Mensajes: 11053
Registrado: 18 Ene 2003

Mensajepor Hermes 14 Sep 2009 11:20

Segunda Parte: Usos Avanzados

Hasta ahora hemos visto lo suficiente para entender cómo podemos construir nuestros propios módulos, donde cargarlos y la forma de cargarlos mediante las herramientas que no proporciona mload. Pero queremos llegar aún más lejos: ya que hemos podido acceder al Starlet con la posibilidad de cargar nuestro propio código ¿por qué no contar con unas herramientas que nos permitan incluso modificar el sistema y saltar las limitaciones que nos impone?

Para entender lo siguiente de forma más completa, nos viene de perlas una información que se puede encontrar en wiibrew.org en lo referente al Starlet, concretamente éste par de enlaces:

PDF 1: http://infocenter.arm.com/help/topic/co ... 26_TRM.pdf

PDF 2: http://rtds.cs.tamu.edu/web_462/techdoc ... S_r1p2.pdf

Para abreviar, me referiré a ellos como PDF 1 y PDF 2, cuando quiera señalar algún dato importante.

También hay un enlace sobre la arquitectura en general: http://www.arm.com/miscPDFs/14128.pdf

Modos de ejecución y vectores de excepción

Nuestros módulos se ejecutan en Modo Usuario, en el cual no tenemos permisos para modificar otras zonas de memoria o modificar ciertos registros y por tanto, no podemos mas que observar el contenido y en ocasiones, ni eso y la única forma de poder hacer algo, es mediante las syscalls que disponemos... ¿pero realmente, es la única forma?

Obviamente, se hace necesario ganar dichos permisos, de hecho, las syscalls que os he descrito anteriormente, no son más que un vía para acceder a una excepción que se produce cuando el ARM intenta ejecutar una instrucción no definida (es decir, una instrucción máquina inexistente), lo que provoca un cambio de modo del procesador y un salto a un punto determinado de la memoria, donde si se dispone de algunos de los permisos que necesitamos para hacer algunas de las cosas interesantes que buscamos :)

En Wii la tabla de excepciones se localiza a partir de la dirección 0xFFFF0000 y según el PDF 2, página 70, estos son los vectores que contiene:


OFFSET 0xFFFF0000 Reset (Modo Supervisor)

OFFSET 0xFFFF0004 Instrucción Indefinida (Modo Indefinido). Las syscalls de Wii que conocemos

OFFSET 0xFFFF0008 Interrupción de Software SWI (Modo Supervisor): Salta a una instrucción de retorno en los IOS, excepto en mload

OFFSET 0xFFFF000C Abort (Prefech) (Modo Abort)

OFFSET 0xFFFF0010 Abort (Data) (Modo Abort)

OFFSET 0xFFFF0014 Reserved

OFFSET 0xFFFF0018 IRQ: Interrupciones (Modo IRQ)

OFFSET 0xFFFF001C FIQ: Interrupciones rápidas (Modo FIQ). En Wii sin uso



Como podemos observar, cada excepción entra en un determinado modo del procesador cuya principal diferencia es, aparte de contar con una serie de permisos que no se tienen en modo de usuario, contar con una serie de registros adicionales, para almacenar la pila o de retorno distintos, aparte de contar con otros registros de estado. Además de los modos que podéis ver ahí, también contamos con el Modo Sistema, que se apoya en los registros del Modo Usuario, aunque evidentemente, la diferencia es que contamos con permisos de los que no disponemos en el Modo Usuario.

Por cierto, algo importante que conviene saber, es que cada vez que se produce una excepción se deshabilitan las interrupciones en el registro CPSR.

Si queréis echarle un vistazo a la tabla de registros disponibles para los distintos modos, en el PDF 2, página 55 la podemos encontrar (modo ARM o 32 bits) y en la 57, la correspondiente al modo Thumb.

Por si no estáis familiarizados con el ARM (yo no es que sea un experto, que conste: mas bien tengo algunas nociones), los ARM cuentan con un modo de instrucciones completo, de 32 bits y un modo de instrucciones reducidas, llamado Thumb de tan solo 16 bits, para economizar memoria y que tiene limitado también el acceso a ciertos registros. El código que compilamos en C se convierte en instrucciones Thumb, mientras que el modo ARM de 32 Bits, queda reservado para operaciones especiales (como las llamadas a las syscalls) o rutinas que queramos optimizar en velocidad. En los PDF podéis encontrar información de los distintos modos de operación, si estáis interesados.

Por cierto, en el mismo PDF 2, página 58, podéis ver el registro de estado CPSR con la descripción de los distintos flags (Modo de ejecución, Thumb, Flags que deshabilitan IRQ y FIQ por software y los típicos flags de estado que tiene cualquier procesador).

El Club de la Lucha

"La primera regla del Club de la Lucha, es que no se habla del Club de la Lucha" ( [+risas] )

Bien, aquí es donde rompo esa primera regla para contaros mis aventuras y desventuras en el desarrollo del driver EHCI y trabajando con el sistema.

Parece obvio que si queremos obtener permisos, deberíamos tratar o bien de buscar algún tipo de exploit raro que nos plantase en Modo Sistema (porque o yo estoy torpe, o en Modo Usuario el registro CPSR pasa de ti cuando tratas de cambiar de modo, cosa que es lógica porque si no ¿para que coño sirven los permisos? [+risas] ) o mucho más fácil, ya que tenemos la posibilidad de crear nuestro cIOS, modificarlo en parte para que alguna de éstas excepciones que hemos visto, nos devolvieran el control.

Hace meses, le sugerí a Waninkoko que se podría utilizar las syscalls, ya fuera utilizando alguna "instrucción" que no se utilizara o modificar alguna de forma que pudiera cumplir con su cometido y además, nos proporcionase algún tipo de control, pero la verdad es que yo no estaba interesado en llevarlo a cabo personalmente y tenía otras preocupaciones.

Pero lamentablemente, el driver EHCI me daba muchos problemas de cuelgues raros que parecían debidos a problemas de sincronización con otros hilos o interrupciones en puntos incómodos (parece ser que es un bug en IOS 36, pero ¿como coño iba a suponer yo eso?) y cuando ya me había contenido como 100 veces de estampar la Wii contra la pared XD, fruto de la impotencia y la desesperación, pensé en mirar la forma de usar interrupciones.

Desensamblando a partir del vector de las syscalls, encuentras fácilmente la tabla de saltos de las funciones de tratamiento de dichas syscalls y en concreto la función correspondiente a os_software_IRQ(), que es la fallaba a la hora de fijar la IRQ 4.

Curiosamente, ésta función utiliza también una tabla de saltos para tratar las diferentes interrupciones y fue fácil darse cuenta que permite fijarlas a partir de IRQ 4 y que había una serie de interrupciones que utilizaban una rutina común de retorno...

Mirando la IRQ 4, vi que hacía un chequeo que si no se cumplía, no permitía fijar los flags correspondientes para dicha IRQ. Así que calculé el salto necesario para esquivar ese chequeo al mismo tiempo que adoptaba la IRQ 9, que estaba fuera de uso, para que saltara a un punto del crt0.s de mload, con el fin de obtener permisos (son los dos parcheos actuales que se hacen en el cIOS installer)

El resultado es que a pesar de que la IRQ 4 estaba habilitada, no funcionaba (porque me faltaba una información que provocó que creara éste hilo hilo_divagaciones-sobre-ehci-y-su-vector-de-interrupcion_1277800), pero la IRQ 9 si que funcionaba perfectamente, como mostraba el LED frontal encendido (es uno de los registros prohibidos en Modo Usuario).

Gracias a isobel supe lo que necesitaba para poder poner en marcha las interrupciones (un flag en uno de los registros EHCI reservados), pero ni se resolvían los problemas y además, tenía otros nuevos, debido a la posibilidad de que se produjeran varias interrupciones de golpe (sobre todo cuando pasé a utilizar mi propio vector de interrupciones).

Sin embargo, desensamblar programas produce que indirectamente, observes cosas que no vas buscando. Entre eso y los PDF, ya me había dado cuenta que el vector de SWI estaba fuera de uso, que aquí se llegaba con las interrupciones deshabilitadas, en diferente modo que con las syscalls y que podría utilizar una serie de instrucciones especiales propias para llamar a las diferentes rutinas. Sobre todo necesitaba acceder a las syscalls relacionadas con la caché y que esto no supusiera una complicación extrema.

La verdad es que recuerdo una conversación con marcan de hace mucho, sobre el lío que estaban suponiendo las funciones de caché (invalidate/flush) por que aparte de lo preceptivo en el ARM, había que tontear con una serie de registros desconocidos, de cuando él desarrollaba bootmii. De hecho, si le echáis un ojo a MINI podéis ver que llama a una función más rara que un perro verde, para cerrar dichas operaciones.

Así que una de mis ideas era ver si podía de alguna manera, llamar a las syscalls sin tener que recurrir a usar la tabla de syscalls para llamarlas de forma directa (que en cada IOS suele estar colocada en un sitio diferente) o tener que copiar parte de MINI... al menos para ese tipo de operaciones de caché (necesarias si vas a modificar cosas en caliente). Y eso hacía aún mas interesante trabajar con SWI en vez de con os_software_IRQ(9), que además, se cepillaba uno de los parámetros pasados como registros en dicha función. Además existía el atractivo de poder capturar mensajes del sistema, pues yo sabía del uso de svc 0xab en la función os_puts() de libcios y ya había visto referencias a ella en los módulos.

Total que ese fue el origen del vector de tratamiento de SWI, donde deposité parte de mi confianza, en la creencia de que si se deshabilitaba las interrupciones en determinados puntos críticos, tal vez se resolvieran los errores del driver EHCI tan extraños (lo mismo tenías un error al minuto, que lo tenías al cabo de una hora... y después cinco errores en un intervalo muy corto y luego otra hora y un bloqueo que requería desenchufar :-?. Raro de cojones )

Pronto me dí cuenta que no iba a ser posible utilizar las syscalls como syscalls (se pasa de Modo Indefinido a Modo Sistema y eso hace que por ejemplo, la pila no coincida, ni el retorno), e incluso que estando en "Modo Dios", que no podía acceder a determinados puntos de la memoria (por ejemplo, no podía parchear dev/es), aunque afortunadamente, el propio vector IRQ ya te mostraba como fijaba los permisos de Acceso en Modo Cliente para todos los dominios y los PDF contenían información sobre ello (PDF 1, página 47. En el crt0.s de mload, podeis ver dos funciones read_access_perm() y write_access_perm() relacionadas con ésto)

Al final tenía todo operativo, pero seguía teniendo un driver inestable... entonces se me ocurrió probar con IOS 38, dado que ya tenía su modulo DIP adaptado para funcionar con uLoader y joder, era muy raro que fuera un problema del driver y ya se me habían acabado las posibilidades (era eso, o golpear la Wii contra la pared, seguida por el disco duro XD) y..... EUREKA! ni un puñetero error de lectura y todo estable: ya solo quedaba limpiar el código de cosas que no fueran necesarias y tratar de añadir al nuevo servicio SWI todo lo que pudiera ser útil y necesario, no solo para trabajar con el driver EHCI o con uLoader, si no con vistas a un uso más profundo y general.

"la regla de oro del Club de la Lucha, es que si no te rindes, puede que no consigas lo que esperabas en un principio, pero también puedes obtener otras recompensas a cambio. A veces incluso, tienes algo de suerte y te llevas mucho más de lo que esperabas en un principio"

Gracias a eso, ahora tengo un driver muy estable, sin un solo error de lectura en mis unidades, trabajando por interrupciones y un conjunto de herramientas que me permiten un acceso pleno y la posibilidad de modificar los IOS en caliente (eso si: después de una buena carga de trabajo y poner en peligro mi estabilidad mental, la salud de la Wii, del disco duro y de la pared [+risas])

Los Servicios SWI de mload

El vector SWI es capturado y preparado para responder a la instrucción en ensamblador "svc x" (también se puede escribir como "swi x", si no me equivoco). Al llamar a la instrucción, se produce una excepción y es responsabilidad de la función de tratamiento de dicha excepción, buscar el código de instrucción y obtener el argumento "x". La instrucción svc existe también en modo thumb, pero no se si por limitación de la excepción o por algún problema, solo he conseguido que funcione si está alineada a 4 bytes. De todas formas, es preferible usar la versión de 32 bits, bajo mi punto de vista.

Por otro lado, conviene saber que las interrupciones deben seguir deshabilitadas por software y que se dispone una pila de 0x900 bytes para trabajar con las diferentes funciones SWI, de los cuales, se gastarán unos cuantos en guardar registros, etc, por lo que hay que tener especial cuidado en procurar que las variables locales, no ocupen mucho espacio.

Ah! y evidentemente... no se debe llamar a una función que hace uso de SWI dentro de una función SWI.

Los servicios de mload, se ofrecen en "svc 0xcc" y en libcios como ya comenté, se dispone de la librería swi_mload para facilitar el acceso a esos servicios. Evidentemente, solo se puede acceder a estas funciones desde el Starlet.

swi_mload

Estas son las funciones ofrecidas:

void swi_mload_add_handler(u8 svc_code, int (*func) (u32 arg0, u32 arg1, u32 arg2, u32 arg3));

Función que permite añadir un handler para tratar las diferentes funciones "svc x", donde x puede se cualquier valor entre 0x0 y 0xff, excepto 0xcc que siempre estará controlado por mload. La razón de ésta función es permitir que otros módulos puedan añadir su propio vector para intercambiar datos, por ejemplo. La función "svc 0xab" se encuentra registrada internamente para redirigir os_puts(), pero puede ser cambiada gracias a ésta función, para ser tratada desde otro lugar.

svc_code: desde 0x0 a 0xff exceptuando 0xcc. Por ejemplo 0xab para "svc 0xab"

func: función que tratará el nuevo servicio SWI. La función recibe los argumentos como registros, desde el r0 al r3 y se ejecutará en Modo Supervisor, por lo que acceder a otros argumentos extras, sería algo complicado (las funciones de C reciben los 4 primeros argumentos en los registros r0 a r3, y el resto en el stack). El valor de retorno es retornado como en cualquier otra función de C (registro r0)

ret: no retorna nada



void * swi_mload_EHCI_data(void);

Esta función retorna la estructura EHCI pre-inicializada desde mload (es lo mismo que se obtenía utilizando la función equivalente en dev/mload)

Su uso se limita a ehcmodule.elf



u32 swi_mload_get_syscall_base(void);

Obtiene la dirección de la tabla de las funciones de syscall. A partir de ésta dirección, podemos llamar a algunas syscalls de forma directa.

Por ejemplo, si queremos llamar a os_sync_before_read(), debemos llamar a la syscall 0x3f. Partiendo de la dirección que devuelve
ésta función, podemos calcular que: syscall_base+ 0x3f*4, es la dirección de la función que necesitamos.

Mas adelante, describiré éste acceso directo de forma practica, aunque podéis observarlo tanto en el crt0.s y el main de ehcmodule, como en los fuentes de mload.

ret: Dirección de la tabla de funciones de las syscalls




u32 swi_mload_get_ios_base(void);

Retorna la versión usada como IOS base (actualmente 38). Puede detectar IOS 36,37,38 y 60. Es útil para poder adaptar los parches que requiramos de forma dinámica (por ejemplo, ehcmodule utiliza ésta función para adaptar la rutina que tratará el vector de interrupción, para cada IOS)

ret: IOS base.



void swi_mload_memcpy(void * dst, void * src, int len);

Función que copia un bloque de memoria desde la dirección fuente a la dirección destino

Lo que la hace especial es:

-1) La copia se hace desde el Modo Supervisor y con las interrupciones deshabilitadas

-2) Se guarda los permisos de acceso y se establecen como Manager, por lo que no se producirán fallos de acceso

-3) Función memcpy() que copia el bloque de memoria

-4) Llamada directa a la función de la syscall os_sync_after_write() para que los datos escritosse copien de la caché a la memoria RAM

-5) Restablece los permisos de acceso originales

En la práctica equivale a copiar un bloque de memoria dentro de la caché a otro punto sin que se produzcan interrupciones, ni fallos de acceso (por falta de permisos) y sincronizando los datos de la caché en escritura con la memoria RAM (para poder utilizarlos con una DMA, por ejemplo)



void swi_mload_memcpy_from_uncached(void * dst, void * src, int len);

Función que copia un bloque de memoria desde la dirección fuente a la dirección destino, invalidando primero, los datos de la caché desde la dirección fuente

Lo que la hace especial es:

-1) La copia se hace desde el Modo Supervisor y con las interrupciones deshabilitadas

-2) Se guarda los permisos de acceso y se establecen como Manager, por lo que no se producirán fallos de acceso

-3) Llamada directa a la función de la syscall os_sync_before_read() para que los datos a leer se actualicen desde la memoria RAM a la caché

-4) Función memcpy() que copia el bloque de memoria

-5) Llamada directa a la función de la syscall os_sync_after_write() para que los datos escritos se copien de la caché a la memoria RAM

-6) Restablece los permisos de acceso originales

En la práctica equivale a copiar un bloque de memoria desde fuera de la caché (datos procedentes de una DMA, por ejemplo) a otro punto sin que se produzcan interrupciones, ni fallos de acceso (por falta de permisos) y sincronizando los datos de la caché en escritura con la memoria RAM.



u32 swi_mload_get_register(u32 addr);

Lectura de un registro de 32 bits

addr: dirección del registro

ret: valor del registro



void swi_mload_put_register(u32 addr, u32 val);

Escritura de un registro de 32 bits

addr: dirección del registro

val: valor a escribir

ret: No devuelve nada



void swi_mload_set_register(u32 addr, u32 val);

Fija bits en un registro de 32 bits. Equivale a *(addr)|=val; Los bits a 1 se fijan

addr: dirección del registro

val: valor con los bits a fijar

ret: No devuelve nada



void swi_mload_clr_register(u32 addr, u32 val);

Borrar bits en un registro de 32 bits. Equivale a *(addr)&=~val. Los bits a 1 se borran

addr: dirección del registro

val: valor con los bits a borrar

ret: No devuelve nada



int swi_mload_call_func(int (*func) (void *in, void *out), void *in, void *out);

Función que se utiliza para invocar a otra función que proporcione el usuario, que será llamada en Modo Supervisor, con las Interrupciones desconectadas. Es muy importante que las interrupciones permanezcan deshabilitadas dentro de dicha función (no se debe modificar los flags del registro CPSR. Gracias a ésta función, se hace innecesario ampliar los servicios SWI de mload en el futuro. Se dispone de una pila con 0x900 bytes, así que mucho cuidado con el tamaño de las variables locales. Tambien se recomienda que las funciones sean relativamente rápidas, para no interferir con la respuesta a las interrupciones

fun: función que será llamada desde el modo supervisor.

in: parámetro pensado como de entrada (puede ser NULL o un dato pasado como dirección o la dirección de un dato o datos)

out: parámetro pensado como de salida (puede ser NULL o un dato pasado como dirección o la dirección de un dato o datos)

ret: retorna el valor devuelto por fun(in,out)

NOTA: Obviamente, no hay una regla escrita que diga que los parámetros in y out no puedan ser utilizados ambos como entrada o ambos como salida, por ejemplo. Su designación actual es a título informativo



void swi_mload_led_on(void);
void swi_mload_led_off(void);
void swi_mload_led_blink(void);


Funciones que permiten controlar, el encendido, apagado y la alternancia (parpadeo), del led frontal.

Solo destacar que la función blink no produce un parpadeo de forma automática, si no que alterna entre encendido y apagado cada vez que es llamada.



void swi_mload_os_software_IRQ9_func( int (*system_mode_func)(void));

Función que permite registrar una función que responderá a la llamada de os_software_IRQ(9). Es decir, si se llama a dicha syscall, la función pasada como argumento será llamada en Modo Sistema y su valor de retorno, se tomará como el retorno de os_software_IRQ(9). ¿Posibles usos? Queda a tu discreción ;)

system_mode_fun: función que recibirá el foco al llamar a os_software_IRQ(9)

ret: No retorna nada.



void * swi_mload_log_func(u32 mode, void *buffer_log, int maxsize_log);

Función para trabajar con el buffer que recibe el texto de log desde la función os_puts().

mode: Modo de operación: 0-> retorna la dirección del log buffer, 1-> borra el log buffer 2-> Asigna un nuevo log buffer

buffer_log: dirección del nuevo log buffer (solo con mode==2)

maxsize_log: tamaño del nuevo log buffer (solo con mode==2)

ret: dirección del actual log buffer (por defecto 4KB). El log buffer es una cadena de texto terminada con el carácter '\0'



Trabajando en Modo Supervisor

La función swi_mload_call_func() y la función swi_mload_add_handler() nos muestran las vías para entrar en Modo Supervisor. Una vez dentro, no podemos utilizar llamadas a syscall y tal vez necesitemos acceder a características especiales que si bien, yo no puedo proporcionar todos los datos, si puedo proporcionar una serie de funciones que pueden ser necesarias y útiles.

Leyendo y escribiendo los permisos de acceso

En el crt0.s de mload, podemos ver:

Código: Seleccionar todo

.align 4
   .code 32
   .global read_access_perm
read_access_perm:
   mrc     p15, 0, r0,c3,c0
   bx   lr

   .align 4
   .code 32
   .global write_access_perm
write_access_perm:
   mcr     p15, 0, r0,c3,c0
   bx   lr


Las funciones se definen como:

u32 read_access_perm(void);

Lee los permisos actuales.

void write_access_perm(u32 flags);

Fija los permisos



Y se puede tener acceso completo con write_access_perm(0xffffffff);. Evidentemente, conviene restablecer los permisos una vez finalizado lo que quiera que estemos haciendo (normalmente, esto solo se utiliza cuando toquemos algún área de memoria prohibida). La descripción de los permisos, se puede ver en PDF 1, página 47

Invalidar la caché de instrucciones

Cuando queramos hacer algún tipo de parche en el código en ejecución, nos puede interesar asegurarnos de que la caché de instrucciones se actualiza con los valores adecuados. Normalmente, podemos controlar la caché de datos, pero no se dispone de ninguna syscall que haga lo mismo con la caché de instrucciones por lo que debemos ocuparnos nosotros mismos. En el PDF 1, página 51 podemos ver las instrucciones del procesador necesarias

En crt0.s de mload podemos ver:

Código: Seleccionar todo
   .align 4
   .code 32
   .global ic_invalidate
ic_invalidate:
   mov      r0, #0
   mcr      p15, 0, r0, c7, c5, 0
   bx      lr


Y se define así:

void ic_invalidate(void);

Función que invalida toda la caché de instrucciones


El problema es que teóricamente, en Wii hay que hacer algo más para actualizar la caché, por lo que lo mejor sería es invocar ésta función y después, llamar a las instrucciones de caché de datos con las que contamos sobre la zona modificada, por si las moscas cojoneras.

Acceso a Syscalls

Pero ¿Con que funciones contamos para trabajar con la caché de datos, si no podemos llamar a las syscalls de forma directa?

Ahí es donde interviene la función swi_mload_get_syscall_base().

Si mirais el main.c y el crt0.s de ehcmodule, podeis ver que en el main.c se declara ésta variable:

u32 syscall_base;


y desde el main() se llama a dicha función de ésta forma (una sola vez):

syscall_base=swi_mload_get_syscall_base(); // obtenemos la dirección de la tabla de syscalls

os_sync_after_write((void *) &syscall_base, 4); // aseguramos la concordancia entre caché y memoria


Con esto ya hemos ajustado lo necesario para poder invocar syscalls de forma directa, utilizando su tabla de saltos. En el crt0.s podemos observar:

Código: Seleccionar todo
   .align 4
   .code 32
        .global direct_syscall
direct_syscall:
   ldr     r12, =syscall_base
   ldr   r12, [r12]
   nop
   ldr     r12, [r12,r11,lsl#2]
   nop
   bx   r12

   .align 4
   .code 32
   .global direct_os_sync_before_read
direct_os_sync_before_read:

   mov     r11, #0x3f
   b   direct_syscall
   
   .align 4
   .code 32
   .global direct_os_sync_after_write
direct_os_sync_after_write:

   mov     r11, #0x40
   b   direct_syscall



Como se puede observar, se utiliza el registro r11 para contener el índice a la syscall, mientras el registro r12 se utiliza para almacenar la dirección de la tabla y calcular el desplazamiento necesario para poder invocar a la función de syscall requerida.

Tenéis que tener en cuenta que las syscalls relacionadas con los hilos, seguramente fallen, al no estar trabajando en el contexto adecuado, pero realmente, no tenéis por que usarlas aquí.

Las syscalls que os he mostrado, se declaran así:


void direct_os_sync_before_read(void* ptr, int size);


Equivalente a os_sync_before_read()


void direct_os_sync_after_write(void* ptr, int size);


Equivalente a os_sync_after_write()



El vector de interrupción

En crt0.s de ehcmodule, se puede observar la rutina interrupt_vector(). La rutina está ajustada para trabajar con IOS 36, pero como se puede observar en main.c en la función copy_int_vect() se llevan a cabo unos parches en crt0.s para poder ajustar la rutina para trabajar con IOS 36,37,38 y 60.

El modo de capturar el vector se realiza en la instrucción "tst r8, #0x1" del vector original, modificando esa instrucción y la siguiente para obtener un "ldr pc, =interrupt_vector" . Podéis ver en main.c "static u32 vector[2]={ 0xE51FF004, 0};" utilizada para llevar a cabo los parches necesarios (el segundo valor, sería la dirección de salto)

El registro r8, almacena los flags de interrupción, después de haber sido enmascarados con la máscara de interrupciones. Por tanto, un bit alzado significa una interrupción en curso.

El vector de interrupciones hace una serie de comprobaciones en cadena y entre ellas está la interrupción EHCI, que responde a la instrucción "tst r8, #0x10"

Por ello hacemos dicha comprobación y si no se ha producido interrupción, hacemos la comprobación "tst r8, #0x1" original (vector del timer) y saltamos al punto conveniente del vector original en virtud al resultado que nos de.

Si la interrupción EHCI está en proceso, la instrucción "bic r8, r8, #0x10" se encarga de que no se vuelva a tratar dicha interrupción más adelante, cuando estemos de vuelta en el vector original de interrupción.

Las intrucciones "mov r2, #0x10" y "str r2, [r7]" tienen el cometido de anular el flag de interrupción en el registro de interrupciones (registro 0x0d800038, podéis observar dichos registros en libcios, concretamente en starlet.h)

Después observamos una serie de instrucciones para preservar la pila, cambiarla por otra interna (de 0x200 bytes) y como preservamos una serie de registros, antes de saltar a _ehci_vector_ , desde la que se volverá a saltar a ehci_vector(), en modo thumb (ehci_vector() se declara en ehci_interrupt.c)

A la vuelta se hace un test con el retorno: si se retorna 1, se llamara a la función int_send_device_message(4) después de haber restablecido la pila, (opción que ahora mismo no se utiliza ya que se invoca a dicha función desde el propio ehci_vector()) pasando después a tratar "tst r8, #0x1", (la interrupción del timer) y retornando en consecuencias al vector original

La función int_send_device_message() tiene el cometido de enviar un mensaje a la cola asociada al dispositivo, si éste se registro con la syscall os_register_event_handler(). En ehcmodule, cuando estamos transfiriendo datos, el hilo ajusta un timer (timeout) y aguarda a que la cola asociada reciba un mensaje, ya sea desde la interrupción o del timer.

Espero que con éstas notas, sepáis entender como trabaja el vector de interrupciones. Obviamente, se hace necesario saber algo de ensamblador de ARM, disponer de un buen desensamblador (como IDA Pro) y hacer lo propio partiendo desde la tabla de excepciones que os puse arriba, y en concreto, desde el vector IRQ para tener una vista más apropiada del tema.

DIP Plugin (cIOS 222/223)

dip_plugin es un módulo binario (no usa formato .elf) desarrollado por Wiigator / Waninkoko para conectar con el módulo DIP y redirigir sus llamadas de forma que podamos simular la lectura de un DVD desde un dispositivo USB, por ejemplo. El código fuente es semi-privado y el autor original no quiere hacerlo público (soy de los pocos que disponen de el), por lo que idee un sistema que permitiera adaptarlo a los diferentes IOS y así daros la posibilidad de poder investigar por vuestra cuenta.

dip_plugin se aloja en la dirección 0x1377E000 de la memoria compartida de mload. Es decir, se aloja al final de la memoria disponible.

dip_plugin necesita conectar con diferentes funciones del módulo DIP, de forma directa, así que recurrí a un sencillo sistema que incluía una tabla con las direcciones de las diferentes rutinas.

Por defecto dip_plugin tiene fijada su tabla para trabajar con el módulo DIP de IOS36, así pues si queremos trabajar con el módulo DIP de IOS38 por ejemplo (el módulo actual, pues el IOS base es el 38), tenemos que identificar el módulo DIP rastreando en la memoria del Starlet en búsqueda de un patrón conocido.

Identificando el módulo DIP

En el caso de uLoader, está preparado para identificar el DIP de IOS 36 y 38. Lo hace de ésta forma:

Código: Seleccionar todo
   mload_seek(0x20207c84, SEEK_SET); // dirección en IOS 38 de la cadena DIP a buscar
   mload_read(patch_datas, 4); // lee 4 bytes sobre un buffer temporal alineado a 32 bytes

   if(patch_datas[0]==0x6e657665)
      {
      is_ios=38; // es IOS 38
      }
   else
      {
      is_ios=36; // suponemos que es IOS 36
      }


La tabla de direcciones

El inicio de dip_plugin se compone de una tabla de direcciones con entradas de 4 bytes, obviamente


+0x00 DI_EmulateCmd -> Función de entrada en dip_plugin (Thumb)

+0x04 0x12340001 -> ID de dip_plugin

+0x08 dvd_read_controlling_data -> dirección de buffer de datos en dip_plugin (IOS 36: 0x2022DDAC IOS 38: 0x2022cdac)

+0x0c handle_di_cmd_reentry+1 -> Punto de retorno a DIP (Thumb) (IOS 36: 0x20201010+1 IOS 38: 0x20200d38+1)

+0x10 addr_ios_shared_alloc_aligned+1 -> Función DIP (Thumb) (IOS 36: 0x20200b9c+1 IOS 38: 0x202008c4+1)

+0x14 addr_ios_shared_free+1 -> Función DIP (Thumb) (IOS 36: 0x20200b70+1 IOS 38: 0x20200898+1)

+0x18 addr_ios_memcpy+1 -> Función DIP (Thumb) (IOS 36: 0x20205dc0+1 IOS 38: 0x20205b80+1)

+0x1c addr_ios_fatal_di_error+1 -> Función DIP (Thumb) (IOS 36: 0x20200048+1 IOS 38: 0x20200048+1)

+0x20 addr_ios_doReadHashEncryptedState+1 -> Función DIP (Thumb) (IOS 36: 0x20202b4c+1 IOS 38: 0x20202874+1)

+0x24 addr_ios_printf+1 -> Función DIP (Thumb) (IOS 36: 0x20203934+1 IOS 38: 0x2020365c+1)

+0x28 Reserved
+0x2c Reserved
+0x30 Reserved
+0x34 Reserved
+0x38 Reserved
+0x3c Reserved

+0x40 in_ES_ioctlv -> Punto de entrada de la función ioctlv del módulo dev/es (se usa con 'Skip IOS' en uLoader)



Conectando el módulo dip_plugin

El módulo DIP es parcheado en el proceso de instalación con el fin de preparar un salto en una zona de memoria con acceso compartido dentro del propio modulo DIP, con el propósito de poder localizar y desviar fácilmente el curso de ejecución de DIP hacia dip_plugin.

Desde uLoader ésto se hace así:

IOS 36 escribió:
memcpy(ios_36, dip_plugin, 4); // copy the entry_point (preserves entry point)
memcpy(dip_plugin, ios_36, 4*10); // copy the adresses from the array

mload_seek(0x1377E000, SEEK_SET); // copy dip_plugin in the starlet
mload_write(dip_plugin,size_dip_plugin);

// enables DIP plugin
mload_seek(0x20209040, SEEK_SET); // enables dip_plugin bypass
mload_write(ios_36, 4);

mload_close();


IOS 38 escribió:memcpy(ios_38, dip_plugin, 4); // copy the entry_point (preserves entry point)
memcpy(dip_plugin, ios_38, 4*10); // copy the adresses from the array

mload_seek(0x1377E000, SEEK_SET); // copy dip_plugin in the starlet
mload_write(dip_plugin,size_dip_plugin);

// enables DIP plugin
mload_seek(0x20208030, SEEK_SET); // enables dip_plugin bypass
mload_write(ios_38, 4);

mload_close();


Como se puede apreciar, uLoader copia la dirección de la función de entrada de dip_plugin a una tabla, luego copia la tabla correspondiente sobre dip_plugin, después aloja dip_plugin en la memoria del Starlet y por último conecta dip_plugin copiando la dirección de su función de entrada en un punto determinado de memoria compartida en DIP (me temo que tendrás que mirar los parches DIP del instalador, desensamblar el módulo DIP (NUS Downloader te permite bajar los IOS desencriptados) y comparar las diferentes direcciones para comprender como trabaja exactamente, si quieres adaptarlo a otros IOS. Si solo buscas la forma de utilizarlo, esto servirá)

El parche dev/es de ioctlv

Desde uLoader tenemos una opción llamada 'Skip IOS' que así como está implementada, tiene poca utilidad. Además, se redirige hacia dip_plugin para economizar y no tener que añadir otro módulo de corte similar.

La función consiste en capturar la función que trata ioctlv en dev/es. Si le echáis un ojo a libogc, podéis observar en es.c algunas funciones que podréis controlar haciendo éste desvío.

Desde mload se hacen una serie de parches que provocan el desvío de ésta función a su crt0.s retornando normlamente, si no se programa una función de tratamiento.

La forma de hacerlo seria:

mload_set_ES_ioctlv_vector(in_ES_ioctlv); // thumb in_ES_ioctlv function
mload_close();


En el módulo del Starlet, ésta función sería:

asm.s

Código: Seleccionar todo
   .align 2
   .code 16
   .global in_ES_ioctlv
   .thumb_func
in_ES_ioctlv:
   
   push {r2-r6}
   push {lr}
   bl ES_ioctlv+1
   pop {r1}
   pop {r2-r6}
   bx r1

   .global out_ES_ioctlv
   .thumb_func
out_ES_ioctlv:
   push   {r4-r6,lr}
   sub   sp, sp, #0x20
   ldr r5, [r0,#8]
   add r1, r0, #0
   ldr r3, = 0x201000D5
   bx r3



es_ioctlv.c

Código: Seleccionar todo
extern int in_ES_ioctlv(void *);
extern int out_ES_ioctlv(void *);

struct _ioctl{
         void *data;
         u32 len;
         };
struct _ipcreq
{                  //ipc struct size: 32
   u32 cmd;         //0
   s32 result;         //4
   union {            //8
      s32 fd;
      u32 req_cmd;
   };
   union {
      struct {
         char *filepath;
         u32 mode;
      } open;
      struct {
         void *data;
         u32 len;
      } read, write;
      struct {
         s32 where;
         s32 whence;
      } seek;
      struct {
         u32 ioctl;
         void *buffer_in;
         u32 len_in;
         void *buffer_io;
         u32 len_io;
      } ioctl;
      struct {
         u32 ioctl;
         u32 argcin;
         u32 argcio;
         struct _ioctl *argv;
      } ioctlv;
      u32 args[5];
   };

   
} ATTRIBUTE_PACKED;

int ES_ioctlv(struct _ipcreq *dat )
{
int r;
u32 ios,version;

ios_sync_before_read(dat, sizeof(struct _ipcreq));

if(dat->ioctlv.ioctl==8) // reboot
   {
        ios_sync_before_read( (void *) dat->ioctlv.argv[0].data, dat->ioctlv.argv[0].len);

   ios=*(((volatile u32 *)dat->ioctlv.argv[0].data)+1) ;
        version=1;

        ios_sync_before_read((void *) 0x3140,8);
   *((volatile u32 *) 0x3140)= ((ios)<<16) | (version & 65535); // write fake IOS version/revision
   *((volatile u32 *) 0x3188)= ((ios)<<16) | (version & 65535); // write fake IOS version/revision
   ios_sync_after_write((void *) 0x3140,4);
   ios_sync_after_write((void *) 0x3188,4);
        return 0;
   }

r=out_ES_ioctlv(dat);
return r;
}



Esto es mas o menos, lo que sucede cuando se activa "Skip IOS" desde uLoader. El código lo podríais incluir en un modulo diferente e investigar otras cosas (la verdad, se le podría sacar un partido enorme a la función, como simular la presencia de IOS que no tenemos instalados y hacer que se ejecuten otros en su lugar, o incluso llevar a cabo la carga de un IOS modificado a conveniencia, aunque eso lo dejo para otros y seguro que lleva muucho trabajo de por medio)

Yo simplemente, me limito a sacar a la luz el tema y explicarlo un poco [+risas]

FIN

Hilo de uLoader

hilo_utilidad-uloader-v3-0c-ocarina-y-forzado-de-video-idioma_1217626#p1715667691
Ultima edición por Hermes el 26 Sep 2009 11:48, editado 19 veces

eduwanke
Avatar de usuario
Habitual
 
Mensajes: 91
Registrado: 29 Oct 2008
Ubicación: Madrid

Mensajepor eduwanke 14 Sep 2009 12:15

¡Grande Hermes, Grande!

No dejas de sorprender a toda la comunidad con tus avances, programaciones y/o mejoras. Macho, que alegría me das con todo lo que puedo leer aquí y lo que llevo aprendido en el añito que llevo así un poco más decentemente con todos vosotros...

Un saludo,

trigui
Avatar de usuario
MegaAdicto!!!
 
Mensajes: 723
Registrado: 30 Ene 2007
Ubicación: Earth

Re:

Mensajepor trigui 14 Sep 2009 12:15

Hermes escribió:(reservado para escribir tochos)

Joder, que parece que estás especulando con el terreno "forístico", toma ya, esperando recalificar el espacio, jejeje.
Gracias por tu trabajo Hermes, eres de los grandes.
Un saludo

Hermes
MegaAdicto!!!
 
Mensajes: 11053
Registrado: 18 Ene 2003

Re: Re:

Mensajepor Hermes 14 Sep 2009 12:22

trigui escribió:
Hermes escribió:(reservado para escribir tochos)

Joder, que parece que estás especulando con el terreno "forístico", toma ya, esperando recalificar el espacio, jejeje.
Gracias por tu trabajo Hermes, eres de los grandes.
Un saludo


Hay que especular con el espacio, porque aparte de que no puedo doble-postear, no tengo forma de controlar el hilo y se que la información va a ser muy extensa... y siempre es mejor que continué poco después a que lo haga dentro de 3 páginas ;) (hay que ser previsor XD )

lamateporunyogur
Avatar de usuario
Load boot.dol/.elf?
 
Mensajes: 923
Registrado: 07 May 2008
Ubicación: IOS36

Mensajepor lamateporunyogur 14 Sep 2009 13:06

Hermes, no dejas de impresionarme. [sonrisa]
Mientras los sceners van cayendo, tu te alzas y continúas la marcha.

Y sin duda no puedes controlar lo que postea la gente. De hecho, me estoy arrepintiendo ahora mismo de postear.
De todos modos, siempre puedes reabrir el hilo en otro creado especialmente por los mods que esté cerrado hasta que termines.

Saludos, sigue así que estoy metiéndome de lleno en tu curso de programación. [beer]

vagomayor
Novato
 
Mensajes: 6
Registrado: 10 Sep 2009

Muy Profesional

Mensajepor vagomayor 14 Sep 2009 14:58

Atención, vagomayor está baneado de toda la web, de modo que no podrá responder. Motivo del ban: troll.
Me encanta tu maestria Hermes muy informativo.

civ
Avatar de usuario
¿Qué joroba?
 
Mensajes: 1896
Registrado: 16 May 2008
Ubicación: Donde yo quiera

Mensajepor civ 14 Sep 2009 22:38

No se lo que has dicho, pero tu madre por si acaso [+risas]

Nah, ahora en serio, tiene una pinta de interesante que tira pa tras, pero como no me entero muy bien...

El caso, buen trabajo [ok] [oki]

Danielc
Avatar de usuario
MiiConsola.com
 
Mensajes: 2508
Registrado: 17 Abr 2008

Mensajepor Danielc 14 Sep 2009 23:57

Muy interesante.. Aunque no lo entienda muy bien, creo que he entendido algo: esto permitiría parchear un IOS en memoria y de esa manera no tener que andar instalando cIOS cada vez que se quiera modificar algo??

Siguiente

Volver a Scene

¿Quién está conectado?

Usuarios navegando por este foro: No hay usuarios registrados visitando el foro y 0 invitados