There are usually two different kinds of breakpoint that a debugger can set: software breakpoints and hardware breakpoints.
A software breakpoint consists of replacing the instruction at the target address with a "break" instruction (e.g. int 3
on x86) and waiting for the CPU to execute it. When that instruction is hit, the CPU raises an exception and transfers control to the debugger. The upside is that you can define as many breakpoints as you want, but the downside is that this requires modifying the program in memory (which may not be possible for programs in read-only memory, or may cause the program to behave differently if it reads its own program memory).
The other kind, a hardware breakpoint, consists of setting a special debug register in the CPU to ask it to break when it hits a specified address. The CPU will automatically raise an exception when the program counter reaches that address. The upside is that no software modification is needed, but the downside is that this relies on a limited resource (debug registers) of which there may not be many. For example, x86 processors typically only have 4 debug address registers, so you can only set 4 hardware breakpoints at a time.
Debuggers typically pick a strategy depending on available resources (e.g. hardware breakpoints for the first 4 breakpoints and software breakpoints thereafter), although many can also be configured to force one particular type of breakpoint. For example, the popular debugger GDB has the hbreak
command to explicitly create hardware breakpoints.