Signals are a communication mechanism between a process and the operating system. When a running program encounters a significant error, the OS sends a signal to that process. Some processes may have a signal handler which does some important tasks before the process leaves the CPU.
Signals and interrupts serve similar purposes, but they differ in their origin and handling mechanisms. Interrupts originate from the processor and are managed by the operating system's kernel. In contrast, signals are generated by the kernel and are managed by the individual process to which they are sent.
There are various types of signals, each corresponding to different events.
This signal in Unix-like operating systems is sent to a process when it attempts to execute an illegal, malformed, unknown, or privileged instruction.
The signal is generated when process tries to access memory location which is not allocated to it.
The following are some typical causes of a segmentation fault:
- Attempting to access a nonexistent memory address (outside process's address space)
- Attempting to access memory the program does not have rights to (such as kernel structures in process context)
- Attempting to write read-only memory (such as code segment)
When you access an array index, C and C++ don't do bound checking. Segmentation faults only happen when you try to read or write to a page that was not allocated (or try to do something on a page which isn't permitted, e.g. trying to write to a read-only page), but since pages are usually pretty big (multiples of a few kilobytes), it often leaves you with lots of room to overflow.
int main() {
// Define an array with a small size
int array[5] = {0, 1, 2, 3, 4};
std::cout << "Original array values:\n";
for (int i = 0; i < 5; i++) {
std::cout << "array[" << i << "] = " << array[i] << "\n";
}
// Overflowing the array
// Writing beyond the bounds of the array
std::cout << "\nWriting beyond the bounds of the array...\n";
for (int i = 0; i < 100;
i++) { // This loop intentionally goes well beyond the array's bounds
array[i] = i;
}
return 0;
}
By setting the followings flag you can find the issue:
set(CMAKE_CXX_FLAGS "-fsanitize=address ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
If an error itself is detected by the program then this signal is
generated using call to abort()
. This signal is also used by
standard library to report an internal error.
assert() function in c++ also uses abort()
to generate this signal.
Error that occurred like division by zero, floating point error.
This signal is also produced when an invalid memory is accessed. It may
seem to be same like SIGSEGV
but in SIGSEGV
, the memory location referenced
is valid but in case of SIGBUS
, memory referenced does not exist
i.e de-referencing a memory location out of memory space.
This signal is sent to process when an invalid argument is passed to a system call.
This signal is send to process when an exception is occurred. This is requested by the debugger to get informed. For example, if a variable changes its value then this will trigger it.
The SIGTERM
signal is a generic signal used to cause program termination.
Unlike SIGKILL
, this signal can be blocked, handled, and ignored. It is
the normal way to politely ask a program to terminate. The shell command
kill generates SIGTERM
by default.
create the signal handler function:
void signalHandler(int signum) {
std::cout << "signal (" << signum << ") received.\n";
// Cleanup and close up logic
exit(signum);
}
setting the handler for a specific signal:
int main() {
// Register signal SIGINT ( or any of SIGILL,SIGINT,SIGSEGV,SIGTERM,SIGABRT,SIGFPE) and signal handler
signal(SIGINT, signalHandler);
}
In the following example signal that is being handled is SIGINT
. This signal is typically sent when the user presses Ctrl+C
in the terminal. The SIGINT_Handler function in the example is specifically set up to catch and handle the SIGINT signal.
#include <csignal>
#include <iostream>
// Signal handler function
void SIGINT_Handler(int signum) {
std::cout << "Interrupt signal (" << signum << ") received.\n";
// Cleanup and close up logic
exit(signum);
}
int main() {
// Register signal SIGINT and signal handler
signal(SIGINT, SIGINT_Handler);
while(1) {
std::cout << "Waiting for signal..." << std::endl;
sleep(1);
}
return 0;
}
#include <csignal>
#include <cstdlib> // for exit()
#include <iostream>
#include <unistd.h> // getpid()
void SIGSEGV_Handler(int signum) {
std::cout << "oh my god! segmenation fault happened" << std::endl;
printf("Process %d got signal %d\n", getpid(), signum);
// kill(getpid(), signum);
exit(signum);
}
int main() {
signal(SIGSEGV, SIGSEGV_Handler);
int *p;
*p = 10;
}
The <csignal>
header file declared the function raise()
to handle a particular signal.
Syntax:
int raise ( int signal_ )
complete example:
sig_atomic_t s_value = 0;
void SIGTERM_Handler(int signum) { s_value = signum; }
int main() {
signal(SIGTERM, SIGTERM_Handler);
std::cout << "Before called Signal = " << s_value << std::endl;
raise(SIGTERM);
std::cout << "After called Signal = " << s_value << std::endl;
}