Segfault采用简单的malloc和免费实现

时间:2018-04-16 15:07:25

标签: assembly memory-management x86 malloc

TLDR;

我使用自定义逻辑过度使用mallocfree。然而,该计划是segfault。有什么我想念的吗?最后,我可以使用简单的mallocfree来调试它,但不使用任何stdio.h功能。为什么会这样?

完整版

我使用malloc编写了一个32位汇编程序来覆盖freeLD_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

然而,当我在32位系统上使用以下内容测试它时,它只会导致segfault

LD_PRELOAD=./alloc-lib.so ls

我决定编写一个简单的C程序来分配一个整数1来测试它,但是甚至不能进行printf函数调用。它segfault也是!

最后,我修改了C程序,使用putchar从新内存位置将字节转储到文件,并使用hexdump验证该值。 putchar API在没有segfault的情况下工作。

#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. 有谁知道为什么segfaultlsprintf 功能调用?假设这可能是因为我没有实现realloc等,那么程序是否只使用标准realloc
  2. 以上测试用例是否合理?我只是使用我的新mallocfree分配整数,将其转储到文件并使用hexdump进行验证?
  3. 这是Programming from Ground Up书中的练习。

0 个答案:

没有答案