The ARM Instrumentation Trace Macrocell(ITM) is used for debugging.
I have here an STM32 Discovery board which is the STM32H747I-DISCO.
This Discovery board has an STM32H747XI: ARM Cortex-M7 + Cortex-M4.

As previously stated, it has 2 cores. Let’s focus on the Cortex-M4 instead rather than the Cortex-M7.

By default, printf() would not work on the SWO pin. Check SWD:
https://developer.arm.com/documentation/101636/0100/Debug-and-Trace/JTAG-SWD-Interface
We can just add:
..\STM32Cube_FW_H7_V1.11.2\Drivers\CMSIS\Core\Include
..\STM32Cube_FW_H7_V1.11.2\Drivers\CMSIS\Device\ST\STM32H7xx\Include
on the MCU/MPU GCC Compiler Include paths
Remember, we only care about the Cortex-M4. (At least, for this rough documentation).

#define STM32H747xx
#define CORE_CM4
#include <stm32h7xx.h>
Let’s redirect the printf output to the SWO by overriding the _write function in syscalls.c.
The default _write function is located in ..\Src\syscalls.c :
attribute((weak)) int _write(int file, char *ptr, int len)

We can override this function, just remove the attribute((weak)).
int _write(int file, char *ptr, int len)
{
(void)file;
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
ITM_SendChar(*ptr++);
}
return len;
}
__STATIC_INLINE uint32_t ITM_SendChar (uint32_t ch) is located in: core_cm4.h header file.

ITM_SendChar() uses the ITM Stimulus Port 0 by default. Don’t forget to set it on the Serial Wire Viewer settings.
Besides ITM_SendChar(), you can also use your own function to redirect it to UART, etc.
For ARM cores without ITM, semihosting could be used.
https://developer.arm.com/documentation/dui0375/g/What-is-Semihosting-/What-is-semihosting-
https://community.st.com/t5/stm32-mcus/how-to-use-semihosting-with-stm32cubeide-and-stm32/ta-p/49742
