Проблема 1 - формат изображения. Когда приложение запускается в исполнение, ОС должна загрузить образ приложения, выяснить его точку входа и запустить его оттуда. Это означает, что ОС должна понимать формат изображения, и между различными ОС существуют разные форматы.
Проблема 2 - доступ к устройствам. После запуска приложение может читать и записывать реестры в CPU, и это все. Чтобы сделать что-нибудь интересное, например, отобразить символ на консоли, ему нужен доступ к устройствам, а это значит, что он должен запросить такой доступ у ОС. У каждой ОС есть свой API, который предлагается для доступа к таким устройствам.
Проблема 3 - инструкции привилегий. Вновь запущенному процессу, возможно, понадобится место в памяти для хранения чего-либо, и не все можно сделать с помощью реестров. Это означает, что он должен выделить оперативную память и настроить перевод с виртуального адреса на физический. Это операции привилегий, которые может выполнять только ОС, и опять-таки, API для доступа к этим службам различается в разных ОС.
Суть в том, что приложения написаны не для процессора, а для набора примитивных сервисов, предлагаемых ОС. альтернатива состоит в том, чтобы писать приложения на основе набора примитивных сервисов, предлагаемых виртуальной машиной, и это приводит к более или менее переносимым приложениям, таким как приложения Java.