Ошибка сегментирования ассемблер

I am learning AT&T x86 assembly language. I am trying to write an assembly program which takes an integer n, and then return the result (n/2+n/3+n/4). Here is what I have done: .text .global _...

I am learning AT&T x86 assembly language. I am trying to write an assembly program which takes an integer n, and then return the result (n/2+n/3+n/4). Here is what I have done:

.text
.global _start
_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax
    mov $0, %esi
    movl $4, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $3, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $2, %ebp
    div %ebp
    addl %eax, %esi
    movl %esi, %eax
    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ret

The problem is I am getting segmentation fault. Where is the problem?

Claudio's user avatar

Claudio

10.4k4 gold badges29 silver badges70 bronze badges

asked Sep 26, 2012 at 15:06

user1700688's user avatar

4

I think the code fails here:

_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax

So, you push $24 (4 bytes) and then call profit, which pushes eip and jumps to profit. Then you pop the value of eip into ebx and the value $24 into eax.

Then, in the end, if jg end branches to end:, then the stack won’t hold a valid return address and ret will fail. You probably need pushl %ebx there too.

    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret

answered Sep 26, 2012 at 15:17

nrz's user avatar

nrznrz

10.4k4 gold badges38 silver badges71 bronze badges

2

You do not appear to be doing function calls correctly. You need to read and understand the x86 ABI (32-bit, 64-bit) particularly the «calling convention» sections.

Also, this is not your immediate problem, but: Don’t write _start, write main as if this were a C program. When you start doing something more complicated, you will want the C library to be available, and that means you have to let it initialize itself. Relatedly, do not make your own system calls; call the wrappers in the C library. That insulates you from low-level changes in the kernel interface, ensures that errno is available, and so on.

answered Sep 26, 2012 at 15:33

zwol's user avatar

zwolzwol

133k36 gold badges244 silver badges353 bronze badges

1

  1. you use ecx without ever explicitly initializing it (I’m not sure if Linux will guarantee the state of ecx when the process starts — looks like it’s 0 in practice if not by rule)
  2. when the program takes the jg end jump near the end of the procedure, the return address is no longer on the stack, so ret will transfer control to some garbage address.

answered Sep 26, 2012 at 15:17

Michael Burr's user avatar

Michael BurrMichael Burr

329k50 gold badges528 silver badges755 bronze badges

Your problem is that you pop the return address off of the stack and when you branch to end you don’t restore it. A quick fix is to add push %ebx there as well.

What you should do is modify your procedure so it uses the calling convention correctly. In Linux, the caller function is expected to clean the arguments from the stack, so your procedure should leave them where they are.

Instead of doing this to get the argument and then restoring the return address later

popl %ebx
popl %eax

You should do this and leave the return address and arguments where they are

movl 4(%esp), %eax

and get rid of the code that pushes the return address back onto the stack. You then should add

subl $4, %esp

after the call to the procedure to remove the argument from the stack. It’s important to follow this convention correctly if you want to be able to call your assembly procedures from other languages.

answered Sep 26, 2012 at 16:21

Dirk Holsopple's user avatar

Dirk HolsoppleDirk Holsopple

8,6211 gold badge23 silver badges37 bronze badges

It looks to me like you have a single pushl before you call profit and then the first thing that profit does is to do two popl instructions. I would expect that this would pop the value you pushed onto the stack as well as the return code so that your ret would not work.

push and pop should be the same number of times.

call pushes the return address onto the stack.

answered Sep 26, 2012 at 15:17

Richard Chambers's user avatar

Richard ChambersRichard Chambers

16.3k4 gold badges77 silver badges105 bronze badges

I am learning AT&T x86 assembly language. I am trying to write an assembly program which takes an integer n, and then return the result (n/2+n/3+n/4). Here is what I have done:

.text
.global _start
_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax
    mov $0, %esi
    movl $4, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $3, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $2, %ebp
    div %ebp
    addl %eax, %esi
    movl %esi, %eax
    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ret

The problem is I am getting segmentation fault. Where is the problem?

Claudio's user avatar

Claudio

10.4k4 gold badges29 silver badges70 bronze badges

asked Sep 26, 2012 at 15:06

user1700688's user avatar

4

I think the code fails here:

_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax

So, you push $24 (4 bytes) and then call profit, which pushes eip and jumps to profit. Then you pop the value of eip into ebx and the value $24 into eax.

Then, in the end, if jg end branches to end:, then the stack won’t hold a valid return address and ret will fail. You probably need pushl %ebx there too.

    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret

answered Sep 26, 2012 at 15:17

nrz's user avatar

nrznrz

10.4k4 gold badges38 silver badges71 bronze badges

2

You do not appear to be doing function calls correctly. You need to read and understand the x86 ABI (32-bit, 64-bit) particularly the «calling convention» sections.

Also, this is not your immediate problem, but: Don’t write _start, write main as if this were a C program. When you start doing something more complicated, you will want the C library to be available, and that means you have to let it initialize itself. Relatedly, do not make your own system calls; call the wrappers in the C library. That insulates you from low-level changes in the kernel interface, ensures that errno is available, and so on.

answered Sep 26, 2012 at 15:33

zwol's user avatar

zwolzwol

133k36 gold badges244 silver badges353 bronze badges

1

  1. you use ecx without ever explicitly initializing it (I’m not sure if Linux will guarantee the state of ecx when the process starts — looks like it’s 0 in practice if not by rule)
  2. when the program takes the jg end jump near the end of the procedure, the return address is no longer on the stack, so ret will transfer control to some garbage address.

answered Sep 26, 2012 at 15:17

Michael Burr's user avatar

Michael BurrMichael Burr

329k50 gold badges528 silver badges755 bronze badges

Your problem is that you pop the return address off of the stack and when you branch to end you don’t restore it. A quick fix is to add push %ebx there as well.

What you should do is modify your procedure so it uses the calling convention correctly. In Linux, the caller function is expected to clean the arguments from the stack, so your procedure should leave them where they are.

Instead of doing this to get the argument and then restoring the return address later

popl %ebx
popl %eax

You should do this and leave the return address and arguments where they are

movl 4(%esp), %eax

and get rid of the code that pushes the return address back onto the stack. You then should add

subl $4, %esp

after the call to the procedure to remove the argument from the stack. It’s important to follow this convention correctly if you want to be able to call your assembly procedures from other languages.

answered Sep 26, 2012 at 16:21

Dirk Holsopple's user avatar

Dirk HolsoppleDirk Holsopple

8,6211 gold badge23 silver badges37 bronze badges

It looks to me like you have a single pushl before you call profit and then the first thing that profit does is to do two popl instructions. I would expect that this would pop the value you pushed onto the stack as well as the return code so that your ret would not work.

push and pop should be the same number of times.

call pushes the return address onto the stack.

answered Sep 26, 2012 at 15:17

Richard Chambers's user avatar

Richard ChambersRichard Chambers

16.3k4 gold badges77 silver badges105 bronze badges

Segmentation faults commonly occur when programs attempt to access memory regions that they are not allowed to access. This article provides an overview of segmentation faults with practical examples. We will discuss how segmentation faults can occur in x86 assembly as well as C along with some debugging techniques.

See the previous article in the series, How to use the ObjDump tool with x86.

What are segmentation faults in x86 assembly?

A segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (for example, attempting to write to a read-only location).

Let us consider the following x86 assembly example.

section .data

message db “Welcome to Segmentation Faults! ”

section .text

global _start

_printMessage:

mov eax, 4

mov ebx, 1

mov ecx, message

mov edx, 32

int 0x80

ret

_start:

call _printMessage

As we can notice, the preceding program calls the subroutine _printMessage when it is executed. When we  read this program without executing, it looks innocent without any evident problems. Let us assemble and link it using the following commands.

nasm print.nasm -o print.o -f elf32

ld print.o -o print -m elf_i386

Now, let us run the program and observe the output.

$ ./print

Welcome to Segmentation Faults! Segmentation fault (core dumped)

As we can notice in the preceding excerpt, there is a segmentation fault when the program is executed.

How to detect segmentation faults in x86 assembly

Segmentation faults always occur during runtime but they can be detected at the code level. The previous sample program that was causing the segmentation faults, is due to lack of an exit routine within the program. So when the program completes executing the code responsible for printing the string, it doesn’t know how to exit and thus lands on some invalid memory address.

Another way to detect segmentation faults is to look for core dumps. Core dumps are usually generated when there is a segmentation fault. Core dumps provide the situation of the program at the time of the crash and thus we will be able to analyze the crash. Core dumps must be enabled on most systems as shown below.

When a segmentation fault occurs, a new core file will be generated as shown below.

$ ./print

Welcome to Segmentation Faults! Segmentation fault (core dumped)

$ ls

core seg seg.nasm  seg.o

$

As shown in the proceeding excerpt, there is a new file named core in the current directory.

How to fix segmentation faults x86 assembly

Segmentation faults can occur due to a variety of problems. Fixing a segmentation fault always depends on the root cause of the segmentation fault. Let us go through the same example we used earlier and attempt to fix the segmentation fault. Following is the original x86 assembly program causing a segmentation fault.

section .data

message db “Welcome to Segmentation Faults! ”

section .text

global _start

_printMessage:

mov eax, 4

mov ebx, 1

mov ecx, message

mov edx, 32

int 0x80

ret

_start:

call _printMessage

As mentioned earlier, there isn’t an exit routine to gracefully exit this program. So, let us add a call to the exit routine immediately after the control is returned from  _printMessage. This looks as follows

section .data

message db “Welcome to Segmentation Faults! ”

section .text

global _start

_printMessage:

mov eax, 4

mov ebx, 1

mov ecx, message

mov edx, 32

int 0x80

ret

_exit:

mov eax, 1

mov ebx, 0

int 0x80

_start:

call _printMessage

call _exit

Notice the additional piece of code added in the preceding excerpt. When _printMessage completes execution, the control will be transferred to the caller and call _exit instruction will be executed, which is responsible for gracefully exiting the program without any segmentation faults. To verify, let us assemble and link the program using the following commands.

nasm print-exit.nasm -o print-exit.o -f elf32

ld print-exit.o -o print-exit -m elf_i386

Run the binary and we should see the following message without any segmentation fault.

$ ./print-exit

Welcome to Segmentation Faults!

$

As mentioned earlier, the solution to fix a segmentation fault always depends on the root cause.

How to fix segmentation fault in c

Segmentation faults in C programs are often seen due to the fact that C programming offers access to low-level memory. Let us consider the following example written in C language.

int main()

{

char *str;

str = “test string”;

*(str+1) = ‘x’;

return 0;

}

The preceding program causes a segmentation fault when it is run. The string variable str in this example stores in read-only part of the data segment and we are attempting to modify read-only memory using the line *(str+1) = ‘x’;

Similarly, segmentation faults can occur when an array out of bound is accessed as shown in the following example.

void main(void)

{

char test[3];

test[4] = ‘A’;

}

This example also leads to a segmentation fault. In addition to it, if the data being passed to the test variable is user-controlled, it can lead to stack-based buffer overflow attacks. Running this program shows the following error due to a security feature called stack cookies.

$ ./arr

*** stack smashing detected ***: terminated

Aborted (core dumped)

$

The preceding excerpt shows that the out-of-bound access on an array can also lead to segfaults. Fixing these issues in C programs again falls back to the reason for the Segfault. We should avoid accessing protected memory regions to minimize segfaults.

How to debug segmentation fault

Let us go through our first x86 example that was causing a segfault to get an overview of debugging segmentation faults using gdb. Let us begin by running the program, so we can get the core dump when the segmentation fault occurs.

$ ./print

Welcome to Segmentation Faults! Segmentation fault (core dumped)

$

Now, a core dump should have been generated. Let us load the core dump along with the target executable as shown in the following command. Loading the executable along with the core dump makes the debugging process much easier.

$ gdb ./print -c core -q

GEF for linux ready, type `gef’ to start, `gef config’ to configure

78 commands loaded for GDB 9.1 using Python engine 3.8

[*] 2 commands could not be loaded, run `gef missing` to know why.

[New LWP 6172]

Core was generated by `./print’.

Program terminated with signal SIGSEGV, Segmentation fault.

#0  0x0804901c in ?? ()

gef➤

As we can notice in the preceding output, the core dump is loaded using GDB and the segmentation fault occurred at the address 0x0804901c. To confirm this, we can check the output of info registers.

gef➤  i r

eax            0x20                0x20

ecx            0x804a000           0x804a000

edx            0x20                0x20

ebx            0x1                 0x1

esp            0xffbe8aa0          0xffbe8aa0

ebp            0x0                 0x0

esi            0x0                 0x0

edi            0x0                 0x0

eip            0x804901c           0x804901c

eflags         0x10202             [ IF RF ]

cs             0x23                0x23

ss             0x2b                0x2b

ds             0x2b                0x2b

es             0x2b                0x2b

fs             0x0                 0x0

gs             0x0                 0x0

gef➤

As highlighted, the eip register contains the same address. This means, the program attempted to execute the instruction at this address and it has resulted in a segmentation fault. Let us go through the disassembly and understand where this instruction is.

First, let us get the list of functions available and identify which function possibly caused the segfault.

gef➤  info functions

All defined functions:

Non-debugging symbols:

0x08049000  _printMessage

0x08049017  _start

0xf7fae560  __kernel_vsyscall

0xf7fae580  __kernel_sigreturn

0xf7fae590  __kernel_rt_sigreturn

0xf7fae9a0  __vdso_gettimeofday

0xf7faecd0  __vdso_time

0xf7faed10  __vdso_clock_gettime

0xf7faf0c0  __vdso_clock_gettime64

0xf7faf470  __vdso_clock_getres

gef➤

As highlighted in the preceding excerpt, the _printMessage and _start functions’ address ranges are close to the address that caused the segmentation fault. So, let us begin with the disassembly of the function _printMessage.

gef➤  disass _printMessage

Dump of assembler code for function _printMessage:

0x08049000 <+0>: mov    eax,0x4

0x08049005 <+5>: mov    ebx,0x1

0x0804900a <+10>: mov    ecx,0x804a000

0x0804900f <+15>: mov    edx,0x20

0x08049014 <+20>: int    0x80

0x08049016 <+22>: ret

End of assembler dump.

gef➤

Let us set a breakpoint at ret instruction and run the program. The following command shows how to setup the breakpoint.

gef➤  b *0x08049016

Breakpoint 1 at 0x8049016

gef➤

Type run to start the program execution.

0x8049009 <_printMessage+9> add    BYTE PTR [ecx+0x804a000], bh

0x804900f <_printMessage+15> mov    edx, 0x20

0x8049014 <_printMessage+20> int    0x80

→  0x8049016 <_printMessage+22> ret

↳   0x804901c                  add    BYTE PTR [eax], al

0x804901e                  add    BYTE PTR [eax], al

0x8049020                  add    BYTE PTR [eax], al

0x8049022                  add    BYTE PTR [eax], al

0x8049024                  add    BYTE PTR [eax], al

0x8049026                  add    BYTE PTR [eax], al

As we can notice in the preceding excerpt, when the ret instruction gets executed, the control gets passed to the region not controlled by the program code leading to unauthorized memory access and thus a segmentation fault.

Conclusion

This article has outlined some basic concepts around segmentation faults in x86 assembly and how one can use them for debugging programs. We have seen various simple examples to better understand the concepts. We briefly discussed core dumps, which can help us to detect and analyze program crashes.

See the next article in this series, How to control the flow of a program in x86 assembly.

Sources

  • https://www.geeksforgeeks.org/core-dump-segmentation-fault-c-cpp/
  • http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html
  • https://embeddedbits.org/linux-core-dump-analysis/

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 16:36, 24 августа 2017.

Ошибка сегментации (англ. Segmentation fault, сокр. segfault, жарг. сегфолт) — ошибка программного обеспечения, возникающая при попытке обращения к недоступным для записи участкам памяти либо при попытке изменения памяти запрещённым способом.

Сегментная адресация памяти является одним из подходов к управлению и защите памяти в операционной системе. Для большинства целей она была вытеснена страничной памятью, однако в документациях по традиции используют термин «Ошибка сегментации». Некоторые операционные системы до сих пор используют сегментацию на некоторых логических уровнях, а страничная память используется в качестве основной политики управления памятью.

Сегментная адресация памяти является одним из подходов к управлению и защите памяти в операционной системе. Для большинства целей она была вытеснена страничной памятью, однако в документациях по традиции используют термин «Ошибка сегментации». Некоторые операционные системы до сих пор используют сегментацию на некоторых логических уровнях, а страничная память используется в качестве основной политики управления памятью.

Общие понятия

Сегментная адресация памяти  — схема логической адресации памяти компьютера в архитектуре x86. Линейный адрес конкретной ячейки памяти, который в некоторых режимах работы процессора будет совпадать с физическим адресом, делится на две части: сегмент и смещение. Сегментом называется условно выделенная область адресного пространства определённого размера, а смещением — адрес ячейки памяти относительно начала сегмента. Базой сегмента называется линейный адрес (адрес относительно всего объёма памяти), который указывает на начало сегмента в адресном пространстве. В результате получается сегментный (логический) адрес, который соответствует линейному адресу база сегмента+смещение и который выставляется процессором на шину адреса.

Ошибка сегментации происходит , когда программа пытается получить доступ к  месту памяти, на которое она не имеет права доступа, или пытается получить доступ к ячейке памяти таким образом, каким не допускается условиями системы.

Термин «сегментация» применяется в разных сферах вычислительной техники. В контексте обсуждаемой темы, термин используется с 1950 года , и относится к адресное пространству программы. Таким образом, при попытке чтения за пределами адресного пространства программы или запись на фрагмент, предназначенного только для чтения сегмента адресного пространства, приводит к ошибке сегментации, отсюда и название.

В системах, использующих аппаратные средства сегментации памяти для обеспечения виртуальной памяти, сбой сегментации возникает, когда оборудование системы обнаруживает попытку программы обратиться к несуществующему сегменту, и пытается сослаться на место вне границ сегмента, или сослаться на место на которое у нее нет разрешений.

В системах , использующих только пейджинг недопустимая страница, как правило, ведет к сегментации, ошибки сегментации или ошибки страницы, в виду устройства алгоритмов системы виртуальной памяти.

Страницы — это области физической памяти фиксированного размера, мельчайшая и неделимая единица памяти, которой может оперировать ОС.

На аппаратном уровне, неисправность возникает по причине реагирования блока управления памяти (MMU) на неправомерный доступ к памяти.

На уровне операционной системы эта ошибка ловится и сигнал передается в блок «offending process», где эта ошибка обрабатывается:

  • В UNIX-подобных операционных системах процесс, обращающийся к недействительным участкам памяти, получает сигнал «SIGSEGV».
  • В Microsoft Windows, процесс, получающий доступ к недействительным участкам памяти, создаёт исключение «STATUS_ACCESS_VIOLATION», и, как правило, предлагает запустить отладчик приложения Dr. Watson, которая показывает пользователю окно с предложением отправить отчет об ошибке Microsoft.

Суммирую можно сказать, когда пользовательский процесс хочет обратиться к памяти, то он просит MMU переадресовать его. Но если полученный адрес ошибочен, — находится вне пределов физического сегмента, или если сегмент не имеет нужных прав (попытка записи в read only-сегмент), — то ОС по умолчанию отправляет сигнал SIGSEGV, что приводит к прерыванию выполнения процесса и выдаче сообщения “segmentation fault”.

Причины сегментации

Наиболее распространенные причины ошибки сегментации:

  • Разыменование нулевых указателей 
  • Попытка доступа к несуществующему адресу памяти (за пределами адресного пространства процесса)
  • Попытка доступа к памяти программой, которая не имеет права на эту часть памяти
  • Попытка записи в память, предназначенной только для чтения

Причины зачастую вызваны ошибками программирования, которые приводят к ошибкам связанными с памятью:

  • Создание операций с разыменованым указателем или создание неинициализированного указателя (указатель, который указывает на случайный адрес памяти)
  • Разыменование или возложение на освобожденной указатель (использование оборванного указателя, который указывает на память, которая была освобождена или перераспределена).
  • Переполнение буфера
  • Переполнение стека
  • Попытка выполнить программу, которая не компилируется правильно. (Некоторые компиляторы будут выводить исполняемый файл , несмотря на наличие ошибок во время компиляции.)

Пример Segmentation Fault

Рассмотрим пример кода на ANSI C, который приводит к ошибке сегментации вследствие присутствия квалификатора Сonst — type:

 const char *s = "hello world";
 *(char *)s = 'H';

Когда программа, содержащая этот код, скомпилирована, строка «hello world» размещена в секции программы с бинарной пометкой «только для чтения». При запуске операционная система помещает её с другими строками и константами в сегмент памяти, предназначенный только для чтения. После запуска переменная s указывает на адрес строки, а попытка присвоить значение символьной константы H через переменную в памяти приводит к ошибке сегментации.

Компиляция и запуск таких программ на OpenBSD 4.0 вызывает следующую ошибку выполнения:

 
$ gcc segfault.c -g -o segfault
$ ./segfault
 Segmentation fault

Вывод отладчика gdb:

 Program received signal SIGSEGV, Segmentation fault.
 0x1c0005c2 in main () at segfault.c:6
 6               *s = 'H';

В отличие от этого, gcc 4.1.1 на GNU/Linux возвращает ошибку ещё во время компиляции:

 $ gcc segfault.c -g -o segfault
 segfault.c: In function ‘main’:
 segfault.c:4: error: assignment of read-only location

Условия, при которых происходят нарушения сегментации, и способы их проявления зависят от операционной системы.
Этот пример кода создаёт нулевой указатель и пытается присвоить значение по несуществующему адресу. Это вызывает ошибки сегментации во время выполнения программы на многих системах.

 int* ptr = (int*)0;
 *ptr = 1;

Ещё один способ вызвать ошибку сегментации заключается в том, чтобы вызвать функцию main рекурсивно, что приведёт к переполнению стека:

Обычно, ошибка сегментации происходит потому что: указатель или нулевой, или указывает на произвольный участок памяти (возможно, потому что не был инициализирован), или указывает на удаленный участок памяти. Например:

 char* p1 = NULL;  /* инициализирован как нулевой, в чем нет ничего плохого, но на многих системах он не может быть разыменован */
 char* p2;  /* вообще не инициализирован */
 char* p3  = (char *)malloc(20);  /* хорошо, участок памяти выделен */
 free(p3);  /* но теперь его больше нет */

Теперь разыменование любого из этих указателей может вызвать ошибку сегментации.
Также, при использовании массивов, если случайно указать в качестве размера массива неинициализированную переменную. Вот пример:

 int main()
 { 
     int const nmax=10;
     int i,n,a[n];
 }

Такая ошибка не прослеживается G++ при компоновке, но при запуске приложения вызовет ошибку сегментации.

Видеопример Segmentation Fault на примере C:

Источники

  • wiki info about Segmentation Fault
  • wiki Segmentation_fault
  • Почему возникает ошибка сегментации
  • Segmentation Fault
  • v
  • t
  • e

Операционные системы

Общая информация
  • Сравнение
  • Разработка криминалистического ПО
  • История
  • Список
  • Хронология
  • Доля использования
Ядро

Архитектура

  • Экзо
  • Гибридное
  • Микро
  • Монолитное
  • Нано

Компоненты

  • Драйвер
  • LKM
  • Микроядро
  • Пространство пользователя
Управление процессами

Аспекты

  • Переключение контекста
  • Прерывание
  • IPC
  • Процесс
  • Блок управления процессом
  • RTOS
  • Поток
  • Разделение времени

Планировщик
задач

  • Многозадачность
  • Упреждающее планирование с фиксированным приоритетом
  • Многоуровневые очереди с обратной связью
  • Вытесняющая многозадачность
  • Round-robin
  • SJN (Shortest job next)
Управление памятью
  • Ошибка на шине
  • Общая ошибка защиты
  • Защита памяти
  • Подкачка страниц
  • Кольца защиты
  • Ошибка сегментации
  • Виртуальная память
  • Страничная память
  • Сегментная адресация памяти
Память для хранения
Файловые системы
  • Загрузчик
  • Дефрагментация
  • Файл устройства
  • Атрибут файла
  • Inode
  • Журнал
  • Раздел диска
  • Виртуальная файловая система
  • VTL
Список
  • AmigaOS
  • Android
  • BeOS
  • BSD
  • Chrome OS
  • Cosmos
  • CP/M
  • Darwin
  • DOS
  • Genode
  • GNU
  • Haiku
  • illumos
  • IncludeOS
  • iOS
    • watchOS
    • tvOS
    • audioOS
  • ITS
  • Linux
  • Mac OS
    • Classic Mac OS
    • macOS
  • MINIX
  • MorphOS
  • MUSIC/SP
  • Nemesis
  • NeXTSTEP
  • NOS
  • OpenVMS
  • ORVYL
  • OS/2
  • OS-9
  • OSv
  • Pick
  • QNX
  • ReactOS
  • RISC OS
  • RSTS/E
  • RSX-11
  • RT-11
  • Solaris
  • TOPS-10/TOPS-20
  • z/TPF
  • TRIPOS
  • UNIX
  • Visi On
  • VM/CMS
  • VS/9
  • webOS
  • Windows
  • Xinu
  • z/OS
Прочее
  • API
  • Computer network
  • HAL
  • Live CD
  • Live USB
  • OS shell
    • CLI
    • GUI
    • NUI
    • TUI
    • VUI
    • ZUI
  • PXE

Каждый раз, когда я пытаюсь запустить свой код, я получаю ошибку сегментации. Может ли кто-нибудь указать мне в правильном направлении, что может быть причиной этого?

Компилятор находится в Linux, из которого я запускаю код через PuTTY.

Я думаю, что это связано с mov dword [esp + #] но не знаю, как это исправить.

%include "asm_io.inc"
segment .data
display db "Area: %d | Points: %d | Probability: %d/%d",10,0
display2 db "Expected Outsome: %d", 0
radiusone db "Enter number ", 0
radiustwo db "Enter number ", 0
radiusthree db "Enter number ", 0
radiusfour db "Enter number ", 0
pointsone db "Enter number ", 0
pointstwo db "Enter number ", 0
pointsthree db "Enter number ", 0
pointsfour db "Enter number ", 0

segment .bss

r1 resd 1 ;Radius
r2 resd 1
r3 resd 1
r4 resd 1
p1 resd 1 ;Points
p2 resd 1
p3 resd 1
p4 resd 1
ca1 resd 1 ;Computed Area
ca2 resd 1
ca3 resd 1
ca4 resd 1
pi1 resd 1 ;radius*radius
pi2 resd 1
pi3 resd 1
pi4 resd 1
pb1 resd 1 ;Probability
pb2 resd 1
pb3 resd 1
pb4 resd 1
eo resd 1 ; Expected Outcome

segment .text
        global  asm_main
        extern printf
asm_main:
        enter   0,0            
        pusha

    mov eax, radiusone
    call print_string
    call read_int
    mov [r1], eax

    mov eax, radiustwo
    call print_string
    call read_int
    mov [r2], eax

    mov eax, radiusthree
    call print_string
    call read_int
    mov [r3], eax

    mov eax, radiusfour
    call print_string
    call read_int
    mov [r4], eax
    ;************************

    mov eax, pointsone
    call print_string
    call read_int
    mov [p1], eax

    mov eax, pointstwo
    call print_string
    call read_int
    mov [p2], eax

    mov eax, pointsthree
    call print_string
    call read_int
    mov [p3], eax

    mov eax, pointsfour
    call print_string
    call read_int
    mov [p4], eax
    ;************************

    mov eax, [r1]
    imul eax, [r1]
    mov [pi1], eax 

    mov eax, [r2]
    imul eax, [r2]
    mov [pi2], eax 

    mov eax, [r3]
    imul eax, [r3]
    mov [pi3], eax 

    mov eax, [r4]
    imul eax, [r4]
    mov [pi4], eax 
    ;**********************

    mov eax, [r1]
    mov [ca1], eax

    mov eax, [ca2]
    sub eax, [pi1]
    mov [ca2], eax 

    mov eax, [ca3]
    sub eax, [pi2]
    mov [ca3], eax 

    mov eax, [ca4]
    sub eax, [pi3]
    mov [ca4], eax 
    ;********************

    mov eax, [r1]
    imul eax, [p1]
    mov [pb1], eax

    mov eax, [r2]
    imul eax, [p2]
    mov [pb2], eax

    mov eax, [r3]
    imul eax, [p3]
    mov [pb3], eax

    mov eax, [r4]
    imul eax, [p4]
    mov [pb4], eax
    ;***********************

    mov eax, [pb1]
    add eax, [pb2]
    add eax, [pb3]
    add eax, [pb4]
    mov [eo], eax
    ;************************

    sub easp 10h

    push dword [pi4]
    push dword [ca1]
    push dword [p1]
    push dword [r1]


    mov dword [esp], display
    call printf
    add esp, 10h    


    popa
    mov     eax, 0           
    leave                     
    ret

Обновление: я добавил изменения в код с использованием всплывающей функции там, где находится вызов, и теперь он избавляется от ошибки сегментации, и теперь я получаю вывод, однако, не те значения, которые мне бы хотелось.

вывод:

Площадь: 134520364 | Очки: 134520380 | Вероятность: 134520396/134520424

Площадь: 134520260 | Очки: 134520276 | Вероятность: 134520292/134520320

Площадь: 134520260 | Очки: 134520276 | Вероятность: 134520292/134520320

когда это должно быть

Площадь: 1 | Очки: 17 | Вероятность: 1/64

У меня нет набора циклов, поэтому я не уверен, почему были напечатаны 3 строки.

UPDATE2:

Внесены изменения в толчок по предложению, и я знаю, что вывод выглядит лучше…

Площадь: 17 | Очки: 1 | Вероятность: 64/134519817

Хотя должно быть:

Площадь: 1 | Очки: 17 | Вероятность: 1/64

как у меня это в стеке

1 , 17 , 1 , 64…. и моя строка для него:

display db «Площадь: %d | Точки: %d | Вероятность: %d/%d»,10,0

Так что похоже, что они случайно размещены

Мне нужно добавить mov dword [esp + 4], display ?

Я изучаю ассемблер AT&T x86. Я пытаюсь написать программу сборки, которая принимает целое число n, а затем возвращает результат (n / 2 + n / 3 + n / 4). Вот что я сделал:

.text
.global _start
_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax
    mov $0, %esi
    movl $4, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $3, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $2, %ebp
    div %ebp
    addl %eax, %esi
    movl %esi, %eax
    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ret

Проблема в том, что я получаю ошибку сегментации. В чем проблема?

5 ответов

Лучший ответ

Я думаю, что код здесь не работает:

_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax

Итак, вы push $24 (4 байта), а затем call profit, который подталкивает eip и переходит к profit. Затем вы вставляете значение eip в ebx, а значение $24 в eax.

Затем, в конце концов, если jg end разветвляется на end:, тогда стек не будет содержать действительный адрес возврата и ret завершится ошибкой. Вам, вероятно, там тоже понадобится pushl %ebx.

    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret


8

nrz
26 Сен 2012 в 19:26

Кажется, вы неправильно выполняете вызовы функций. Вам необходимо прочитать и понять x86 ABI (32-разрядный, < a href = «http://www.x86-64.org/documentation/abi.pdf» rel = «nofollow»> 64-bit ), в частности, разделы «соглашения о вызовах».

Кроме того, это не ваша непосредственная проблема, но: не пишите _start, пишите main, как если бы это была программа на языке C. Когда вы начнете делать что-то более сложное, вы захотите, чтобы библиотека C была доступна, а это означает, что вы должны позволить ей инициализировать себя. Соответственно, не выполняйте свои собственные системные вызовы; вызвать оболочки в библиотеке C. Это изолирует вас от низкоуровневых изменений в интерфейсе ядра, гарантирует, что errno доступен, и так далее.


2

zwol
26 Сен 2012 в 19:33

  1. вы используете ecx без его явной инициализации (я не уверен, что Linux гарантирует состояние ecx при запуске процесса — похоже, что на практике это 0, если не по правилу)
  2. когда программа выполняет переход jg end ближе к концу процедуры, адрес возврата больше не находится в стеке, поэтому ret передаст управление на какой-то мусорный адрес.


2

Michael Burr
26 Сен 2012 в 19:37

Ваша проблема в том, что вы выталкиваете адрес возврата из стека, и когда вы переходите в конец, вы не восстанавливаете его. Быстрое решение — добавить туда push %ebx.

Что вам следует сделать, так это изменить вашу процедуру, чтобы она правильно использовала соглашение о вызовах. В Linux ожидается, что вызывающая функция очистит аргументы из стека, поэтому ваша процедура должна оставить их на месте.

Вместо того, чтобы делать это, чтобы получить аргумент, а затем восстановить адрес возврата позже

popl %ebx
popl %eax

Вы должны сделать это и оставить обратный адрес и аргументы там, где они есть.

movl 4(%esp), %eax

И избавьтесь от кода, который возвращает адрес возврата обратно в стек. Затем вы должны добавить

subl $4, %esp

После вызова процедуры удаления аргумента из стека. Важно правильно следовать этому соглашению, если вы хотите иметь возможность вызывать свои процедуры сборки с других языков.


2

Dirk Holsopple
26 Сен 2012 в 20:21

Мне кажется, у вас есть один pushl, прежде чем вы коллируете профит, а затем первое, что делает профит, — это выполнение двух инструкций popl. Я ожидал, что это вытолкнет значение, которое вы поместили в стек, а также код возврата, так что ваш ret не будет работать.

Push и pop должны повторяться одинаковое количество раз.

Call помещает адрес возврата в стек.


1

Richard Chambers
26 Сен 2012 в 19:17

  • Печать

Страницы: [1]   Вниз

Тема: Segmentation fault в ассемблере.  (Прочитано 1476 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн
Белый пони

Задача из книжки: с помощью функции найти максимальный элемент в массиве и вывести его как exit code.

Написал так:

#  "0" - is the end symbol
#
# OUTPUT: max value as exit code

.section .data

items1:
 .long  10, 14, 100, 44, 3, 113, 5, 0

.section .text

.globl _start
.globl findmax

_start:
#pushing only parameter to the stack
 pushl items1
 call findmax        # result is in %eax
 addl $4, %esp       # scrubbing parameter from the stack

#putting result as exit code to %ebx
 movl %eax, %ebx
 movl $1, %eax
 int $0x80

# "find maximum" function
#
# INPUT: single parameter - pointer to .long data, ended with a zero
#
# VARIABLES:   
#        %ecx - current pointer
#        (%ebx - current value)?
#        %eax - current greater value

#
# RETURN VALUE: maximum number from data array

findmax:
# ENTER FUNCTION
# storing %esp (in %ebp) and %ebp (in the stack)
 pushl %ebp
 movl  %esp, %ebp

 movl 8(%ebp), %ecx  # setting initial data pointer to %ecx
 movl (%ecx), %eax   # setting first element as maximum

 start_loop:
 addl $4, %ecx       # set pointer to the next element
 cmpl $0, (%ecx)     # if "0" jump to loop ending
 je end_loop

 cmpl %eax, (%ecx)
 jle start_loop

 movl (%ecx), %eax

end_loop:
#EXIT FUNCTION
#maximum is already in %eax as return value
#restoring %esp (from %ebp) and %ebp (from the stack)
 movl %ebp, %esp
 popl %ebp
 ret


Собирается и линкуется нормально, а при исполнении выдаёт ошибку сегментации :(

root@serg:/atest# as findmax.s -o findmax.o
root@serg:/atest# ld findmax.o -o findmax
root@serg:/atest# ./findmax
Segmentation fault
root@serg:/atest# echo $?
139

Пробовал комментить куски кода. И похоже что ошибка появляется на строке » movl (%ecx), %eax»:

...
findmax:
# ENTER FUNCTION
# storing %esp (in %ebp) and %ebp (in the stack)
 pushl %ebp
 movl  %esp, %ebp

 movl 8(%ebp), %ecx  # setting initial data pointer to %ecx
 movl (%ecx), %eax  # setting first element as maximum  <---------------- тут       

 #start_loop:
# addl $4, %ecx       # set pointer to the next element
# cmpl $0, (%ecx)     # if "0" jump to loop ending
# je end_loop
...


Как так?  Я неправильно записываю адрес первого элемента массива в %ecx ?


andrey_p

После команды

movl 8(%ebp), %ecxв %ecx — 10. Пользуйся gdb: ассемблить надо так:

as findmax.s -g -o findmax.o

. Полезные команды в gdb — l, b, i reg, i reg ebp, ну и еще куча.

Если так:

movl %ebp, %ecx
 addl $8, %ecx
то не коредампит, но и не работает :) Дальше сам, а то у меня  на ассемблер сегодня с утра что-то не… эээ… не хочется. )))


Пользователь решил продолжить мысль 07 Июня 2011, 13:36:28:


Что-то я понял, что давненько не брал в руки ассемблера… :)

Попробовал, но что-то не могу сообразить (от ассемблера мешанина в голове только осталась, надо бы поправить). Могу только порекомендовать свободную книжку Programming from Ground Up — там на странице 31 как-раз аналогичная программа, но без вызова функции — это дальше.

« Последнее редактирование: 07 Июня 2011, 13:36:28 от andrey_p »


Оффлайн
Белый пони

После команды
movl 8(%ebp), %ecxв %ecx — 10. Пользуйся gdb: ассемблить надо так: as findmax.s -g -o findmax.o. Полезные команды в gdb — l, b, i reg, i reg ebp, ну и еще куча.

Если так:

movl %ebp, %ecx
 addl $8, %ecx
то не коредампит, но и не работает :) Дальше сам, а то у меня  на ассемблер сегодня с утра что-то не… эээ… не хочется. )))


Пользователь решил продолжить мысль [time]Tue Jun  7 13:36:28 2011[/time]:


Что-то я понял, что давненько не брал в руки ассемблера… :)

Попробовал, но что-то не могу сообразить (от ассемблера мешанина в голове только осталась, надо бы поправить). Могу только порекомендовать свободную книжку Programming from Ground Up — там на странице 31 как-раз аналогичная программа, но без вызова функции — это дальше.

Спасибо!

Я как раз по этой книжке и учусь :) Там есть программа поиска максимума, а потом в качестве упражнения советуют сделать её же, только с использованием функции, которой в качестве параметра надо передать указатель на несколько значений.

Покопался с gdb. Похоже проблема в том, что после:

pushl items1в стек пишется первое значение из items1 (10), а не адрес первого элемента. Как записать в стек именно указатель (адрес items1) ?

P.S. Это заметил, копируя параметр из стека в регистр, и проверяя значения регистров с помощью команд

i reg

и т.д.
Как посмотреть напрямую значения локальных переменных с стеке, или просто несколько «верхних» значений со стека я так и не понял :(  Пробовал

info f

до и после

pushl

‘a  , пишет, что локальные переменные по неизвестном адресу :(

« Последнее редактирование: 07 Июня 2011, 14:40:15 от Белый пони »


andrey_p

Гугля по теме gdb и ассемблер, нашел еще одну свободную книжку — Introduction to Computer Organization with x86-64 Assembly Language & GNU/Linux.
В ней аппендикс как-раз полностью посвящен этому вопросу. Вообщем все можно, но сложновато немного.


Оффлайн
Белый пони

Гугля по теме gdb и ассемблер, нашел еще одну свободную книжку — Introduction to Computer Organization with x86-64 Assembly Language & GNU/Linux.
В ней аппендикс как-раз полностью посвящен этому вопросу. Вообщем все можно, но сложновато немного.

Спасибо за помощь! Книжку скачал:)
Что бы передать в качестве параметра адрес массива, а не первый элемент, надо было просто приписать

$

:

pushl $items1вместо

pushl items1 :)


  • Печать

Страницы: [1]   Вверх

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Ошибка сегментирования debian
  • Ошибка сегментирования core dumped
  • Ошибка сегментации это
  • Ошибка сегментации debian
  • Ошибка се 34878 0 ps4 что делать

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии