Тестирование простого malloc и бесплатной реализации

Я написал 32-битную ассемблерную программу для замены malloc и free с использованием LD_PRELOAD.

        # PURPOSE:  Program to replace malloc and free using LD_PRELOAD
        #
        # NOTES:    The programs using these routines will ask for a certain
        #           size of memory. We actually use more than that size, but we
        #           put it at the beginning, before the pointer we hand back.
        #           We add a Size field and an Available/Unavailable marker. So
        #           the memory looks like this:
        #
        #           #########################################################
        #           #Available Marker#Size of Memory#Actual Memory Locations#
        #           #########################################################
        #                                            ^Returned pointer points
        #                                             here
        #
        #           The pointer we return only points to the actual locations
        #           requested to make it easier for the calling program. It
        #           also allows us to change our structure without the calling
        #           program having to change at all.
        #
        #           The _alloc-lib-test.c program can test it:
        #           Dump the output and read it using hexdump.


        .section .data

# Global variables

# This points to the beginning of the memory we are managing

heap_begin:
        .long 0

# This points to one location past the memory we are managing

current_break:
        .long 0

# Structure information

        .equ HEADER_SIZE, 8       # Size of space for memory region header
        .equ HDR_AVAIL_OFFSET, 0  # Location of the "Available" flag in the
                                  # header
        .equ HDR_SIZE_OFFSET, 4   # Location of the size field in the header

# Constants

        .equ UNAVAILABLE, 0  # This is the number we will use to mark space
                             # that has been given out
        .equ AVAILABLE, 1    # This is the number we will use to mark space
                             # that has been returned, and is available for
                             # giving
        .equ SYS_BRK, 45     # break system call

        .equ LINUX_SYSCALL, 0x80

        .section .text

# Functions

# allocate_init starts

        # PURPOSE:  Call this function to initialize the functions
        #           (specifically, this sets heap_begin and current_break).
        #
        # PARAMS:   None
        #
        # RETURN:   None

        .globl allocate_init
        .type allocate_init,@function

allocate_init:

        pushl %ebp                  # Standard function stuff
        movl  %esp, %ebp

        # If the brk system call is called with 0 in %ebx, it returns the last
        # valid usable address

        movl  $SYS_BRK, %eax        # Find out where the break is
        movl  $0, %ebx
        int   $LINUX_SYSCALL
        incl  %eax                  # %eax now has the last valid address, and
                                    # we want the memory location after that
        movl  %eax, current_break   # Store the current break
        movl  %eax, heap_begin      # Store the current break as our first
                                    # address. This will cause the allocate
                                    # function to get more memory from Linux
                                    # the first time it is run
        movl  %ebp, %esp            # Exit the function
        popl  %ebp
        ret

# allocate_init ends

# allocate starts

        # PURPOSE:  This function is used to grab a section of memory. It
        #           checks to see if there are any free blocks, and, if not,
        #           it asks Linux for a new one.
        #
        # PARAMS:   This function has one parameter - the size of the memory
        #           block we want to allocate
        #
        # RETURN:   This function returns the address of the allocated memory
        #           in %eax. If there is no memory available, it will return 0
        #           in %eax
        #
        #           %ecx - size of the requested memory (first/only parameter)
        #           %eax - current memory region being examined
        #           %ebx - current break position
        #           %edx - size of current memory region
        #
        # We scan through each memory region starting with heap_begin. We look
        # at the size of each one, and if it has been allocated. If it's big
        # enough for the requested size, and its available, it grabs that one.
        # If it does not find a region large enough, it asks Linux for more
        # memory. In that case, it moves current_break up

        .globl malloc
        .type malloc,@function

        # Stack position of the memory size to allocate

        .equ ST_MEM_SIZE, 8

malloc:

        pushl %ebp                     # Standard function stuff
        movl  %esp, %ebp

        movl  $0, %edi
        movl  heap_begin, %eax         # If heap_begin is 0, call allocate_init
        cmpl  %eax, %edi
        je    heap_empty
        jne   heap_nonempty

heap_empty:

        call allocate_init

heap_nonempty:

        movl  ST_MEM_SIZE(%ebp), %ecx  # %ecx will hold the size we are looking
                                       # for (which is the first and only
                                       # parameter)

        movl  heap_begin, %eax         # %eax will hold the current
                                       # search location

        movl  current_break, %ebx      # %ebx will hold the current break

alloc_loop_begin:                      # Here we iterate through each memory
                                       # region

        cmpl  %ebx, %eax               # Need more memory if these are equal
        je    move_to_break

        # Grab the size of this memory

        movl  HDR_SIZE_OFFSET(%eax), %edx

        # If the space is unavailable, go to the next one

        cmpl  $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)
        je    next_location

        cmpl  %edx, %ecx               # If the space is available, compare the
        jle   allocate_here            # size to the needed size. If its big
                                       # enough, go to allocate_here

next_location:

        addl  $HEADER_SIZE, %eax       # The total size of the memory region
        addl  %edx, %eax               # is the sum of the size that was
                                       # requested when this block was created
                                       # (currently stored in %edx), plus
                                       # another 8 bytes for the header (4 for
                                       # the Available/Unavailable flag, and 4
                                       # for the Size of the region). So,
                                       # adding %edx and $8 to %eax will get
                                       # the address of the next memory region

        jmp   alloc_loop_begin         # Go look at the next location

allocate_here:                         # If we've made it here, that means that
                                       # the region header of the region to
                                       # allocate is in %eax

        movl  $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)  # Mark space as unavailable

        addl  $HEADER_SIZE, %eax       # Move %eax past the header to the
                                       # usable memory (since that's what we
                                       # return)

        movl  %ebp, %esp               # Return from the function
        popl  %ebp
        ret

# If we've made it here, that means that we have exhausted all addressable
# memory, and we need to ask for more. %ebx holds the current endpoint of the
# data, and %ecx holds its size

move_to_break:                    # We need to increase %ebx to where we _want_
                                  # memory to end, so we add space for the
        addl  $HEADER_SIZE, %ebx  # headers structure and add space to the
                                  # break for the data requested
        addl  %ecx, %ebx


        # Now its time to ask Linux for more memory

        pushl %eax                # Save needed registers
        pushl %ecx
        pushl %ebx

        movl  $SYS_BRK, %eax      # Reset the break (%ebx has the requested
                                  # break point)
        int   $LINUX_SYSCALL

# Under normal conditions, this should return the new break in %eax, which will
# be either 0 if it fails, or it will be equal to or larger than we asked for.
# We don't care in this program where it actually sets the break, so as long as
# %eax isn't 0, we don't care what it is

        cmpl  $0, %eax            # Check for error conditions
        je    error

        popl  %ebx                # Restore saved registers
        popl  %ecx
        popl  %eax

        # Set this memory as unavailable, since we're about to give it away

        movl  $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)

        # Set the size of the memory

        movl  %ecx, HDR_SIZE_OFFSET(%eax)

        # Move %eax to the actual start of usable memory.
        # %eax now holds the return value

        addl  $HEADER_SIZE, %eax

        movl  %ebx, current_break  # Save the new break

        movl  %ebp, %esp           # Return the function
        popl  %ebp
        ret

error:

        movl  $0, %eax             # On error, we return zero
        movl  %ebp, %esp
        popl  %ebp
        ret

# allocate ends

# deallocate starts

        # PURPOSE:  The purpose of this function is to give back a region of
        #           memory to the pool after we're done using it.
        #
        # PARAMS:   The only parameter is the address of the memory we want to
        #           return to the memory pool.
        #
        # RETURN:   There is no return value
        #
        # If you remember, we actually hand the program the start of the memory
        # that they can use, which is 8 storage locations after the actual
        # start of the memory region. All we have to do is go back 8 locations
        # and mark that memory as available, so that the allocate function
        # knows it can use it.

        .globl free
        .type free,@function

        # Stack position of the memory region to free

        .equ ST_MEMORY_SEG, 4

free:

        # Since the function is so simple, we don't need any of the fancy
        # function stuff

        # Get the address of the memory to free (normally this is 8(%ebp), but
        # since we didn't push %ebp or move %esp to %ebp, we can just do
        # 4(%esp))

        movl  ST_MEMORY_SEG(%esp), %eax

        # Get the pointer to the real beginning of the memory

        subl  $HEADER_SIZE, %eax

        # Mark it as available

        movl  $AVAILABLE, HDR_AVAIL_OFFSET(%eax)

        ret

# deallocate ends

Это кросс-компилируется в 64-битной системе следующим образом:

as --32 alloc-lib.asm -o alloc-lib.o
ld --shared "-melf_i386" alloc-lib.o -o alloc-lib.so

Однако это просто приводит к segfault, когда я тестирую его на 32-битной системе, используя что-то вроде:

LD_PRELOAD=./alloc-lib.so ls

Я решил написать простую программу на C, чтобы выделить целое число 1 для проверки, но не смог даже вызвать функцию printf. Это тоже segfaults! Наконец, я модифицировал программу C, чтобы выгрузить байты из новой области памяти в файл, и проверил значение с помощью hexdump.

#include <stdlib.h>
#include <stdio.h>

int main(){
    int *ptra;
    char *cptra;
    ptra = malloc(4);
    *ptra = 1;
    cptra = ptra;
    putchar(*cptra);
    putchar(*(cptra + 1));
    putchar(*(cptra + 2));
    putchar(*(cptra + 3));
    free(ptra);
}
  1. Кто-нибудь знает, почему у него segfault с ls и printf вызов функции?
  2. Разумный ли этот тестовый пример?

Это длинный пост, но я подумал, что важно знать, как я могу это сделать. Пока malloc и freeABI верны, я не думаю, что здесь должна быть какая-то проблема, верно? Что-то не так с этими предположениями?

Операционная система: Linux 32 бит

Одна из потенциальных причин, указанных @fuz, заключалась в том, что я не реализую realloc.

Nishant 18.04.2018 19:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
414
1

Ответы 1

1) ABI ia32 требует, чтобы bx, si, di сохранялись при вызовах функций; вы назначаете bx в allocate_init () и di в malloc (), не сохраняя их. Иногда вы можете обойтись без сохранения регистров, а иногда нет.

2) Google: вероятно, откроет множество наборов тестов распределения, которые вы могли бы применить; но поскольку этот действительно указал на проблему, это хорошее начало.

PS: я сделал визуальный осмотр; недостаточно деталей (ОС, версия, ...) для воспроизведения.

Нет ни одного ABI IA-32 (см. вики-теги x86 для ссылок на документы ABI / соглашения о вызовах). Но да, почти все они согласны с тем, что EBX, ESI и EDI сохраняют вызовы (весь 32-битный регистр, а не только 16-битные подрегистры, хотя я думаю, вы имели в виду это как сокращенное).

Peter Cordes 18.04.2018 06:36

Другие вопросы по теме