GNU/Linux >> Tutoriales Linux >  >> Linux

Cómo implementar malloc determinista

Me pregunto qué estás tratando de lograr. Si su proceso es determinista, entonces el patrón de asignación/desasignación debería ser el mismo.

La única diferencia posible podría ser la dirección devuelta por malloc . Pero probablemente no debería depender de ellos (la forma más fácil es no usar punteros como mapa clave u otra estructura de datos). E incluso entonces, solo debería haber diferencia si la asignación no se realiza a través de sbrk (la glibc usa anónimo mmap para grandes asignaciones), o si está utilizando mmap (ya que por defecto la dirección es seleccionada por el núcleo).

Si realmente desea tener exactamente la misma dirección, una opción es tener un búfer estático grande y escribir un asignador personalizado que use la memoria de este búfer. Esto tiene la desventaja de obligarte a saber de antemano la cantidad máxima de memoria que necesitarás. En un ejecutable que no sea PIE (gcc -fno-pie -no-pie ), un búfer estático tendrá la misma dirección cada vez. Para un ejecutable PIE, puede deshabilitar la aleatorización del diseño del espacio de direcciones del kernel para cargar programas. En una biblioteca compartida, deshabilitar ASLR y ejecutar el mismo programa dos veces debería conducir a las mismas opciones por parte del enlazador dinámico sobre dónde asignar bibliotecas.

Si no sabe de antemano el tamaño máximo de la memoria que desea usar, o si no desea volver a compilar cada vez que aumenta este tamaño, también puede usar mmap para mapear un búfer anónimo grande en una dirección fija. Simplemente pase el tamaño del búfer y la dirección para usar como parámetro para su proceso y use la memoria devuelta para implementar su propio malloc encima.

static void* malloc_buffer = NULL;
static size_t malloc_buffer_len = 0;

void* malloc(size_t size) {
    // Use malloc_buffer & malloc_buffer_len to implement your
    // own allocator. If you don't read uninitialized memory,
    // it can be deterministic.
    return memory;
}

int main(int argc, char** argv) {
    size_t buf_size = 0;
    uintptr_t buf_addr = 0;
    for (int i = 0; i < argv; ++i) {
        if (strcmp(argv[i], "--malloc-size") == 0) {
            buf_size = atoi(argv[++i]);
        }
        if (strcmp(argv[i], "--malloc-addr") == 0) {
            buf_addr = atoi(argv[++i]);
        }
    }

    malloc_buffer = mmap((void*)buf_addr, buf_size, PROT_WRITE|PROT_READ,
                         MAP_FIXED|MAP_PRIVATE, 0, 0);
    // editor's note: omit MAP_FIXED since you're checking the result anyway
    if (malloc_buffer == MAP_FAILED || malloc_buffer != (void*)but_addr) {
        // Could not get requested memory block, fail.
        exit(1);
    }

    malloc_size = buf_size;
}

Usando MAP_FIXED , le estamos diciendo al núcleo que reemplace cualquier asignación existente que se superponga con esta nueva en buf_addr .

(Nota del editor:MAP_FIXED probablemente no es lo que quieres . Especificando buf_addr como pista en lugar de NULL ya pide esa dirección si es posible. Con MAP_FIXED , mmap devolverá un error o la dirección que le dio. El malloc_buffer != (void*)but_addr check tiene sentido para el non-FIXED caso, que no reemplazará un mapeo existente de su código o una biblioteca compartida o cualquier otra cosa. Linux 4.17 introdujo MAP_FIXED_NOREPLACE que puede usar para hacer que mmap devuelva un error en lugar de la memoria en la dirección incorrecta que no desea usar. Pero deje el check-in para que su código funcione en kernels más antiguos).

Si usa este bloque para implementar su propio malloc y no usa otra operación no determinista en su código, puede tener un control completo de los valores del puntero.

Esto supone que su patrón de uso de malloc / free es determinista. Y que no utilice bibliotecas que no sean deterministas.

Sin embargo, creo que una solución más simple es mantener sus algoritmos deterministas y no depender de las direcciones. Esto es posible. Trabajé en un proyecto a gran escala en el que varias computadoras tenían que actualizar el estado de manera determinista (para que cada programa tuviera el mismo estado, mientras solo transmitía entradas). Si no usa el puntero para otras cosas además de hacer referencia a objetos (lo más importante es nunca usar el valor del puntero para nada, ni como un hash, ni como una clave en un mapa, ...), entonces su estado seguirá siendo determinista .

A menos que lo que quiera hacer sea poder tomar una instantánea de toda la memoria del proceso y hacer una diferencia binaria para detectar la divergencia. Creo que es una mala idea, porque ¿cómo sabrás que ambos han llegado al mismo punto en su cálculo? Es mucho más fácil comparar la salida, o hacer que el proceso pueda calcular un hash del estado y usarlo para verificar que estén sincronizados porque puede controlar cuándo se hace esto (y por lo tanto también se vuelve determinista, de lo contrario, su medición no es determinista).


Lo que no es determinista no es solo malloc pero mmap (la llamada al sistema básica para obtener más espacio de memoria; no es una función, es una llamada al sistema, por lo que es elemental o atómica desde el punto de vista de la aplicación, por lo que no puede reescribirla dentro de la aplicación) debido a la aleatorización del diseño del espacio de direcciones en Linux.

Podrías desactivarlo con

 echo 0 > /proc/sys/kernel/randomize_va_space

como root, o a través de sysctl.

Si no deshabilita la aleatorización del diseño del espacio de direcciones, está atascado.

E hiciste una pregunta similar anteriormente, donde expliqué que tu malloc -s no siempre será determinista.

Sigo pensando que para algunas aplicaciones prácticas, malloc no puede ser determinista. Imagine, por ejemplo, un programa que tenga una tabla hash codificada por el pid -s de los procesos secundarios que está iniciando. La colisión en esa tabla no será la misma en todos tus procesos, etc.

Así que creo que no lograrás hacer malloc determinista en su sentido, intente lo que intente (a menos que se limite a una clase muy limitada de aplicaciones para controlar, tan limitada que su software no será muy útil).


En pocas palabras, como han dicho otros:si la ejecución de las instrucciones de su programa es determinista, entonces la memoria devuelta por malloc() será determinista. Eso supone que la implementación de su sistema no tiene alguna llamada a random() O algo por el estilo. Si no está seguro, lea el código o la documentación del malloc de su sistema. .

Esto es con la posible excepción de ASLR, como también han dicho otros. Si no tiene privilegios de root, puede deshabilitarlos por proceso a través del personality(2) syscall y el parámetro ADDR_NO_RANDOMIZE. Consulte aquí para obtener más información sobre las personalidades.

Editar:también debería decir, si no lo sabe:lo que está haciendo se llama bisimulación y es una técnica bien estudiada. Si no conocía la terminología, podría ser útil tener esa palabra clave para buscar.


Linux
  1. ¿Cómo leer una dirección IP al revés?

  2. Cómo iniciar sesión en cPanel

  3. Cómo negar una dirección IP en cPanel

  4. Cómo poner en la lista negra una dirección IP en SmarterMail

  5. Cómo incluir en la lista blanca una dirección IP en SmarterMail

Cómo cambiar la dirección MAC en Linux

Cómo cambiar la dirección IP en Linux

Cómo obtener su dirección IP en Linux

Cómo encontrar la dirección IP en Linux

Cómo:Administración de red de FreeBSD

¿Cómo agregar una nueva dirección IP en Virtualmin?