If it was raised by raise it continues just after the raise()
More precisely, the Linux kernel returns to the very first instruction after the syscall instruction.
Let's GDB it.
signal_return.c
#include <stdio.h> /* puts */
#include <stdlib.h> /* EXIT_SUCCESS */
#include <signal.h> /* signal, raise, SIGSEGV */
#include <unistd.h> /* write, STDOUT_FILENO */
void signal_handler(int sig) {
    (void)sig;
    const char msg[] = "signal received\n";
    write(STDOUT_FILENO, msg, sizeof(msg));
    /* The handler automatically disables handling of future signals.
     * So we set it again here. */
    signal(SIGSEGV, signal_handler);
}
int main(int argc, char **argv) {
    (void)argv;
    signal(SIGSEGV, signal_handler);
    if (argc > 1) {
        *(int *)0 = 1;
    } else {
        raise(SIGSEGV);
    }
    puts("after");
    return EXIT_SUCCESS;
}
Compile:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o signal_return.out signal_return.c
Run without arguments to do a raise(SIGSEGV);:
./signal_return.out
output:
signal received
after
Now I run it through GDB with GDB Dashboar to make it easier to interpret things:
gdb \
  -ex 'set pagination off' \
  -ex 'handle all nostop' \
  -ex 'dashboard -layout source assembly' \
  -ex 'b signal_handler' \
  -ex 'run' \
  -ex 'fin' \
  signal_return.out
handle all nostop stops GDB from stopping at signals for us: How to handle all signals in GDB
After the fin we are left in some internal signal handling machinery, so I just ni a bunch of times.
The first time we come out back to main is:
26     puts("after");
27     return EXIT_SUCCESS;
28 }
─── Assembly ─────────────────────────────────────────────────────────────
0x0000555555555280 main+56 jmp    0x55555555528c <main+68>
0x0000555555555282 main+58 mov    $0xb,%edi
0x0000555555555287 main+63 call   0x555555555090 <raise@plt>
0x000055555555528c main+68 lea    0xd71(%rip),%rax        # 0x555555556004
0x0000555555555293 main+75 mov    %rax,%rdi
0x0000555555555296 main+78 call   0x5555555550a0 <puts@plt>
0x000055555555529b main+83 mov    $0x0,%eax
──────────────────────────────────────────────────────────────────────────
>>> 
we are at the lea. We can see where that fits into main with:
disas /m
which gives:
18      int main(int argc, char **argv) {
   0x0000555555555248 <+0>:     endbr64 
   0x000055555555524c <+4>:     push   %rbp
   0x000055555555524d <+5>:     mov    %rsp,%rbp
   0x0000555555555250 <+8>:     sub    $0x10,%rsp
   0x0000555555555254 <+12>:    mov    %edi,-0x4(%rbp)
   0x0000555555555257 <+15>:    mov    %rsi,-0x10(%rbp)
19          (void)argv;
20          signal(SIGSEGV, signal_handler);
   0x000055555555525b <+19>:    lea    -0x99(%rip),%rax        # 0x5555555551c9 <signal_handler>
   0x0000555555555262 <+26>:    mov    %rax,%rsi
   0x0000555555555265 <+29>:    mov    $0xb,%edi
   0x000055555555526a <+34>:    call   0x5555555550d0 <__sysv_signal@plt>
21          if (argc > 1) {
   0x000055555555526f <+39>:    cmpl   $0x1,-0x4(%rbp)
   0x0000555555555273 <+43>:    jle    0x555555555282 <main+58>
22              *(int *)0 = 1;
   0x0000555555555275 <+45>:    mov    $0x0,%eax
   0x000055555555527a <+50>:    movl   $0x1,(%rax)
   0x0000555555555280 <+56>:    jmp    0x55555555528c <main+68>
23          } else {
24              raise(SIGSEGV);
   0x0000555555555282 <+58>:    mov    $0xb,%edi
   0x0000555555555287 <+63>:    call   0x555555555090 <raise@plt>
25          }
26          puts("after");
=> 0x000055555555528c <+68>:    lea    0xd71(%rip),%rax        # 0x555555556004
   0x0000555555555293 <+75>:    mov    %rax,%rdi
   0x0000555555555296 <+78>:    call   0x5555555550a0 <puts@plt>
27          return EXIT_SUCCESS;
   0x000055555555529b <+83>:    mov    $0x0,%eax
28      }
   0x00005555555552a0 <+88>:    leave  
   0x00005555555552a1 <+89>:    ret
so it is clear that we returned to the very first instruction after the call to the C library function signal:
call   0x555555555090 <raise@plt>
This appears to happen right at the instruction level. By using: https://askubuntu.com/questions/487222/how-to-install-debug-symbols-for-installed-packages/1434174#1434174 we can step into raise until the syscall with some effort:
0x00007ffff7e07a7a __pthread_kill_implementation+240 syscall
0x00007ffff7e07a7c __pthread_kill_implementation+242 mov    %eax,%r13d
After +240 we go into the signal. And after the signal handler returns, the very first instruction executed is +242. TODO: compile a minimal freestanding assembly example to finish it off ;-)
If it was raised by an instruction, it goes back and re-runs the instruction
Running the program with an argument:
./signal_return.out 1
leads to an infinite loop of:
signal received
signal received
signal received
We therefore understand that we must be going back to the very offending instruction.
Let's try to walk into the instruction ourselves this time:
gdb \
  -ex 'set pagination off' \
  -ex 'handle all nostop' \
  -ex 'dashboard -layout source assembly' \
  -ex 'b signal_handler' \
  -ex 'start' \
  -args signal_return.out 1
We are trying to write to address 0 at line 22:
18 int main(int argc, char **argv) {
19     (void)argv;
20     signal(SIGSEGV, signal_handler);
21     if (argc > 1) {
22         *(int *)0 = 1;
23     } else {
24         raise(SIGSEGV);
25     }
26     puts("after");
27     return EXIT_SUCCESS;
28 }
─── Assembly ─────────────────────────────────────────────
0x000055555555526f main+39 cmpl   $0x1,-0x4(%rbp)
0x0000555555555273 main+43 jle    0x555555555282 <main+58>
0x0000555555555275 main+45 mov    $0x0,%eax
0x000055555555527a main+50 movl   $0x1,(%rax)
Now after I si here, we hit the breakpoint.
After I fin we are left at a weird:
0x00007ffff7db3520 __restore_rt+0 mov    $0xf,%rax
0x00007ffff7db3527 __restore_rt+7 syscall 
0x00007ffff7db3529 __restore_rt+9 nopl   0x0(%rax)
which makes system call number 0xf = 15. A quick peek at the syscall table teaches us that this is rt_sigreturn, and man rt_sigreturn tells us a bit about the code injection madness done by the kernel.
After two more sis we are out and back to the exact offending instruction:
22         *(int *)0 = 1;
23     } else {
24         raise(SIGSEGV);
25     }
26     puts("after");
27     return EXIT_SUCCESS;
28 }
─── Assembly ─────────────────────────────────────────────
0x000055555555526f main+39 cmpl   $0x1,-0x4(%rbp)
0x0000555555555273 main+43 jle    0x555555555282 <main+58>
0x0000555555555275 main+45 mov    $0x0,%eax
0x000055555555527a main+50 movl   $0x1,(%rax)
Returning to the offending instruction allows us both to handle the signal and get a core dump
As mentioned at: Linux: handling a segmentation fault and getting a core dump this default behavior is not bad, because what you might want to do on SIGSEGV is:
For this to work like that you would want to remove the signal(SIGSEGV, signal_handler); call from the handler. This way it comes out and blows up as desired instead of looping forever. That was only for demonstration purposes.
Tested on Ubuntu 22.04 x86_64.